Files
mcr/internal/server/admin_routes.go
Kyle Isom dddc66f31b Phases 5, 6, 8: OCI pull/push paths and admin REST API
Phase 5 (OCI pull): internal/oci/ package with manifest GET/HEAD by
tag/digest, blob GET/HEAD with repo membership check, tag listing with
OCI pagination, catalog listing. Multi-segment repo names via
parseOCIPath() right-split routing. DB query layer in
internal/db/repository.go.

Phase 6 (OCI push): blob uploads (monolithic and chunked) with
uploadManager tracking in-progress BlobWriters, manifest push
implementing full ARCHITECTURE.md §5 flow in a single SQLite
transaction (create repo, upsert manifest, populate manifest_blobs,
atomic tag move). Digest verification on both blob commit and manifest
push-by-digest.

Phase 8 (admin REST): /v1 endpoints for auth (login/logout/health),
repository management (list/detail/delete), policy CRUD with engine
reload, audit log listing with filters, GC trigger/status stubs.
RequireAdmin middleware, platform-standard error format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:25:18 -07:00

57 lines
1.9 KiB
Go

package server
import (
"github.com/go-chi/chi/v5"
"git.wntrmute.dev/kyle/mcr/internal/db"
)
// AdminDeps holds the dependencies needed by admin routes.
type AdminDeps struct {
DB *db.DB
Login LoginClient
Engine PolicyReloader
AuditFn AuditFunc
GCState *GCState
}
// MountAdminRoutes adds admin REST endpoints to the router.
// Auth middleware is applied at the route group level.
func MountAdminRoutes(r chi.Router, validator TokenValidator, serviceName string, deps AdminDeps) {
// Health endpoint - no auth required.
r.Get("/v1/health", AdminHealthHandler())
// Auth endpoints - no bearer auth required (login uses credentials).
r.Post("/v1/auth/login", AdminLoginHandler(deps.Login))
// Authenticated endpoints.
r.Route("/v1", func(v1 chi.Router) {
v1.Use(RequireAuth(validator, serviceName))
// Logout.
v1.Post("/auth/logout", AdminLogoutHandler())
// Repositories - list and detail require auth, delete requires admin.
v1.Get("/repositories", AdminListReposHandler(deps.DB))
v1.Get("/repositories/*", AdminGetRepoHandler(deps.DB))
v1.With(RequireAdmin()).Delete("/repositories/*", AdminDeleteRepoHandler(deps.DB, deps.AuditFn))
// Policy - all require admin.
v1.Route("/policy/rules", func(pr chi.Router) {
pr.Use(RequireAdmin())
pr.Get("/", AdminListPolicyRulesHandler(deps.DB))
pr.Post("/", AdminCreatePolicyRuleHandler(deps.DB, deps.Engine, deps.AuditFn))
pr.Get("/{id}", AdminGetPolicyRuleHandler(deps.DB))
pr.Patch("/{id}", AdminUpdatePolicyRuleHandler(deps.DB, deps.Engine, deps.AuditFn))
pr.Delete("/{id}", AdminDeletePolicyRuleHandler(deps.DB, deps.Engine, deps.AuditFn))
})
// Audit - requires admin.
v1.With(RequireAdmin()).Get("/audit", AdminListAuditHandler(deps.DB))
// GC - requires admin.
v1.With(RequireAdmin()).Post("/gc", AdminTriggerGCHandler(deps.GCState))
v1.With(RequireAdmin()).Get("/gc/status", AdminGCStatusHandler(deps.GCState))
})
}