59 lines
2.0 KiB
Go
59 lines
2.0 KiB
Go
package meta
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"slices"
|
|
|
|
"nadir/internal/module"
|
|
|
|
"github.com/danielgtaylor/huma/v2"
|
|
)
|
|
|
|
// ModuleInfo describes a registered module: its stable ID, display name, and
|
|
// the permissions it exposes. The frontend uses this to drive navigation and
|
|
// render the role/permission matrix.
|
|
type ModuleInfo struct {
|
|
ID string `json:"id" example:"system" doc:"Stable module identifier"`
|
|
Name string `json:"name" example:"System" doc:"Human-readable module name"`
|
|
Permissions []string `json:"permissions" doc:"Permissions this module exposes (never includes the \"*\" wildcard)"`
|
|
}
|
|
|
|
type ModulesOutput struct {
|
|
Body struct {
|
|
Modules []ModuleInfo `json:"modules" doc:"Registered modules, sorted by ID"`
|
|
}
|
|
}
|
|
|
|
// Register adds the read-only module-discovery endpoint. It is intentionally
|
|
// public: it exposes only the API's static shape (module IDs and permission
|
|
// vocabulary), the same information already served by /openapi.json. The module
|
|
// list is fixed at startup, so the response is computed once here.
|
|
func Register(api huma.API, mods []module.Module) {
|
|
infos := make([]ModuleInfo, 0, len(mods))
|
|
for _, m := range mods {
|
|
perms := m.Permissions()
|
|
ps := make([]string, len(perms))
|
|
for i, p := range perms {
|
|
ps[i] = string(p)
|
|
}
|
|
infos = append(infos, ModuleInfo{ID: m.ID(), Name: module.Title(m.ID()), Permissions: ps})
|
|
}
|
|
slices.SortFunc(infos, func(a, b ModuleInfo) int { return cmp.Compare(a.ID, b.ID) })
|
|
|
|
huma.Register(api, huma.Operation{
|
|
OperationID: "list-modules",
|
|
Method: "GET",
|
|
Path: "/api/_modules",
|
|
Summary: "List registered modules",
|
|
Description: "Returns every registered module with its ID, display name, " +
|
|
"and exported permissions. Public (same static shape as /openapi.json); " +
|
|
"used by the frontend for navigation and the role/permission matrix.",
|
|
Tags: []string{"Meta"},
|
|
}, func(ctx context.Context, _ *struct{}) (*ModulesOutput, error) {
|
|
out := &ModulesOutput{}
|
|
out.Body.Modules = infos
|
|
return out, nil
|
|
})
|
|
}
|