Files
homelab-frontend/internal/serviceadapter/session_adapter.go
T
rockandAdmin Bot a24e4f7174
CI / CI (push) Successful in 3m15s
feat(sessions): SessionAdapter — REST CRUD for sessions/milestones/episodes (#51)
Closes #50

## Changes

- `internal/sessions/handler.go` — new Handler; 7 endpoints dispatched via `X-Upstream-Path`; writes to `poimen-memory` via `/memory/ingest`; reads from `poimen-graphql` via GraphQL `hybridSearch`/`entity`
- `internal/serviceadapter/session_adapter.go` — `NewSessionAdapter()` + `GetSessionSpec()` with 5 resources
- `cmd/gateway/main.go` — register sessions adapter after memory adapter
- `internal/serviceadapter/adapters.go` — add `sessions` case to factory
- `internal/sessions/handler_test.go` — 30 tests, all pass

## Design

- `X-Service: sessions` + `X-Resource: <sessions|session|milestones|episodes|episode>`
- IDs via query params (`?session_id=`, `?episode_id=`) — dispatcher does not substitute path params
- `episode_id = sha256(session_id+milestone_id+attempt)` per §4.14 Q6
- Content JSON-encoded (injection-safe)
- `tracing.NewTransport` wired for cross-service trace propagation
- GraphQL `errors` array detected, surfaced as 502
- limit capped at 200

---------

Co-authored-by: Admin Bot <[email protected]>
Reviewed-on: #51
2026-09-28 01:53:20 +00:00

108 lines
3.1 KiB
Go

package serviceadapter
import (
"forgejo.riotpiao.com/rock/homelab-frontend/internal/sessions"
)
// NewSessionAdapter creates a ServiceAdapter for the sessions service.
//
// Route mapping (all via X-Service: sessions):
//
// X-Resource Verb upstream path handler
// ──────────────────────────────────────────────────────────────
// sessions POST /v1/sessions/create create session
// sessions GET /v1/sessions/list list sessions (?status=,?cwd=)
// session GET /v1/sessions/get get session (?session_id=)
// session PATCH /v1/sessions/patch update session(?session_id=)
// milestones POST /v1/sessions/milestones/create create milestone(?session_id=)
// episodes GET /v1/sessions/episodes/list list episodes (?session_id=)
// episode GET /v1/sessions/episodes/get get episode (?session_id=,?episode_id=)
//
// Path params (:id, :eid) are passed as query params because the ServiceAdapter
// dispatcher does not substitute URL path parameters.
func NewSessionAdapter() *ServiceAdapter {
return &ServiceAdapter{
Namespace: "poimen",
ServiceName: "sessions",
Handler: sessions.NewHandler(),
Spec: *GetSessionSpec(),
}
}
// GetSessionSpec returns the ServiceAdapter spec for the sessions service.
func GetSessionSpec() *Spec {
return &Spec{
ServiceName: "sessions",
Upstream: Upstream{
URL: "internal://sessions",
TimeoutSeconds: 30,
},
Auth: Auth{
Required: true,
Capability: "sessions:read",
},
Resources: []Resource{
{
Name: "sessions",
Methods: []Method{
{
Verb: "POST",
UpstreamPath: "/v1/sessions/create",
Auth: &Auth{Required: true, Capability: "sessions:write"},
},
{
Verb: "GET",
UpstreamPath: "/v1/sessions/list",
Auth: &Auth{Required: true, Capability: "sessions:read"},
},
},
},
{
Name: "session",
Methods: []Method{
{
Verb: "GET",
UpstreamPath: "/v1/sessions/get",
Auth: &Auth{Required: true, Capability: "sessions:read"},
},
{
Verb: "PATCH",
UpstreamPath: "/v1/sessions/patch",
Auth: &Auth{Required: true, Capability: "sessions:write"},
},
},
},
{
Name: "milestones",
Methods: []Method{
{
Verb: "POST",
UpstreamPath: "/v1/sessions/milestones/create",
Auth: &Auth{Required: true, Capability: "sessions:write"},
},
},
},
{
Name: "episodes",
Methods: []Method{
{
Verb: "GET",
UpstreamPath: "/v1/sessions/episodes/list",
Auth: &Auth{Required: true, Capability: "sessions:read"},
},
},
},
{
Name: "episode",
Methods: []Method{
{
Verb: "GET",
UpstreamPath: "/v1/sessions/episodes/get",
Auth: &Auth{Required: true, Capability: "sessions:read"},
},
},
},
},
}
}