- 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)
162 lines
4.4 KiB
Go
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)
|
|
)
|