NextMyDay/internal/core/server/registry.go
Claus Lohmar d11e14295b feat(core): add the store, identity plumbing and the door (SPEC-003/004)
- store: SQLite (WAL, foreign keys ON, CGO-free driver) + the embedded
  migration runner; each module's migrations are name-prefixed (D-012)
- audit: the append-only audit log; the actor threads via context (R-1)
- users: the user_id/GUID/email anchors (D-013)
- config: the YAML settings file (D-035) + validation
- httpx: cookies · loopback detection · trusted-proxy XFF · CSRF · security
  headers · redaction
- middleware: correlation id · audit · rate limiting
- mail: the invite/recovery mailer seam
- server: the core DOOR — the resolution switch, the dispatch registry
  (RegisterModule), the role gates, /setup · enroll/login · recovery flows,
  the reserved `static` workflow, and the loopback-only bootstrap (D-025)
2026-10-14 10:53:51 +00:00

162 lines
4.4 KiB
Go

package server
import (
"net/http"
"git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/routing"
)
// Handler is a dispatched module function. It receives the door's resolved
// RequestContext — identification and routing, never a raw path (rule of the
// door). A non-nil error is logged and answered as 500.
type Handler func(w http.ResponseWriter, r *http.Request, rc *RequestContext) error
// Registry is the door's dispatch surface: the pure routing.Registry (so
// routing.Dispatch can resolve a Resolution) plus the function lookup that
// turns a resolved (module, function) into a Handler.
//
// SPEC-008 owns the full, discovered registry; this is the minimal in-memory
// implementation that makes the door exercisable.
type Registry interface {
// routing.Registry is the whitelist the pure model consults.
routing.Registry
// Handler returns the function registered for (module, function).
Handler(module, function string) (Handler, bool)
// CoreHandler returns the handler for a core-owned workflow (home, access,
// setup, login, enroll, …) — the reserved level-1 routes.
CoreHandler(workflow string) (Handler, bool)
}
// MapModule is one in-memory module: its routing.Module whitelist plus its
// function handlers.
type MapModule struct {
name string
deflt string
routes map[string]string
actions map[string]string
handlers map[string]Handler
}
// NewModule starts a module definition.
func NewModule(name string) *MapModule {
return &MapModule{
name: name,
routes: make(map[string]string),
actions: make(map[string]string),
handlers: make(map[string]Handler),
}
}
// Name implements routing.Module.
func (m *MapModule) Name() string { return m.name }
// SetDefault declares the function (and handler) for an absent/trailing PARAM3.
func (m *MapModule) SetDefault(fn string, h Handler) *MapModule {
m.deflt = fn
if h != nil {
m.handlers[fn] = h
}
return m
}
// AddRoute registers a whitelisted path-mode function.
func (m *MapModule) AddRoute(name, fn string, h Handler) *MapModule {
m.routes[name] = fn
if h != nil {
m.handlers[fn] = h
}
return m
}
// AddAction registers a whitelisted action-mode function.
func (m *MapModule) AddAction(name, fn string, h Handler) *MapModule {
m.actions[name] = fn
if h != nil {
m.handlers[fn] = h
}
return m
}
// Route implements routing.Module.
func (m *MapModule) Route(function string, tail routing.Params) (string, routing.Params, bool) {
if function == "" {
if m.deflt == "" {
return "", nil, false
}
return m.deflt, tail, true
}
if fn, ok := m.routes[function]; ok {
return fn, tail, true
}
return "", nil, false
}
// Action implements routing.Module.
func (m *MapModule) Action(action string) (string, bool) {
fn, ok := m.actions[action]
return fn, ok
}
// MapRegistry is the minimal in-memory Registry.
type MapRegistry struct {
modules map[string]*MapModule
core map[string]Handler
}
// NewMapRegistry returns an empty registry.
func NewMapRegistry() *MapRegistry {
return &MapRegistry{
modules: make(map[string]*MapModule),
core: make(map[string]Handler),
}
}
// RegisterModule adds a module under PARAM2.
func (r *MapRegistry) RegisterModule(m *MapModule) *MapRegistry {
r.modules[m.name] = m
return r
}
// RegisterCore adds a core-owned workflow handler (home, access, setup, login,
// enroll).
func (r *MapRegistry) RegisterCore(workflow string, h Handler) *MapRegistry {
r.core[workflow] = h
return r
}
// module returns the registered MapModule (nil when absent). It is the
// package-internal handle a registration helper uses to EXTEND an existing
// module — e.g. the SPEC-006 `auth` module gaining SPEC-007's `recover` action.
func (r *MapRegistry) module(name string) *MapModule { return r.modules[name] }
// Module implements routing.Registry. An unknown module is "not found" → 404.
func (r *MapRegistry) Module(name string) (routing.Module, bool) {
m, ok := r.modules[name]
if !ok {
return nil, false
}
return m, true
}
// Handler returns the function handler for (module, function).
func (r *MapRegistry) Handler(module, function string) (Handler, bool) {
m, ok := r.modules[module]
if !ok {
return nil, false
}
h, ok := m.handlers[function]
return h, ok
}
// CoreHandler returns the core handler for a reserved workflow.
func (r *MapRegistry) CoreHandler(workflow string) (Handler, bool) {
h, ok := r.core[workflow]
return h, ok
}
var (
_ Registry = (*MapRegistry)(nil)
_ routing.Module = (*MapModule)(nil)
)