NextWks/src/core/admin/handlers.go

125 lines
3.4 KiB
Go

package admin
import (
"encoding/json"
"log/slog"
"net/http"
"strings"
)
// Handler bundles admin HTTP handlers and their dependencies.
type Handler struct {
store *UserStore
syncWriter *SyncWriter
logger *slog.Logger
}
// NewHandler creates a new admin Handler.
func NewHandler(store *UserStore, syncWriter *SyncWriter, logger *slog.Logger) *Handler {
return &Handler{
store: store,
syncWriter: syncWriter,
logger: logger,
}
}
// RegisterRoutes mounts admin routes on the given mux.
func (h *Handler) RegisterRoutes(mux *http.ServeMux, authMiddleware func(http.Handler) http.Handler) {
// Admin API (protected by bearer token)
mux.Handle("GET /admin/api/users", authMiddleware(http.HandlerFunc(h.listUsers)))
mux.Handle("POST /admin/api/users", authMiddleware(http.HandlerFunc(h.createUsers)))
mux.Handle("DELETE /admin/api/users/{username}", authMiddleware(http.HandlerFunc(h.deleteUser)))
mux.Handle("GET /admin/api/health", authMiddleware(http.HandlerFunc(h.adminHealth)))
}
// --- API Handlers ---
func (h *Handler) listUsers(w http.ResponseWriter, r *http.Request) {
users, err := h.store.List()
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
if users == nil {
users = []User{}
}
writeJSON(w, http.StatusOK, users)
}
type createUsersResponse struct {
Results []CreateUserResult `json:"results"`
}
func (h *Handler) createUsers(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON body"})
return
}
if len(req.Users) == 0 {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "no users provided"})
return
}
results := h.store.Create(req)
// Sync to Authelia YAML
if err := h.syncWriter.Sync(); err != nil {
h.logger.Error("sync failed after create", "error", err)
}
writeJSON(w, http.StatusCreated, createUsersResponse{Results: results})
}
func (h *Handler) deleteUser(w http.ResponseWriter, r *http.Request) {
username := r.PathValue("username")
if username == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "username is required"})
return
}
if err := h.store.Delete(username); err != nil {
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
return
}
// Sync to Authelia YAML
if err := h.syncWriter.Sync(); err != nil {
h.logger.Error("sync failed after delete", "error", err)
}
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted", "username": username})
}
func (h *Handler) adminHealth(w http.ResponseWriter, r *http.Request) {
count, err := h.store.Count()
status := "ok"
if err != nil {
status = "degraded"
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": status,
"user_count": count,
"authelia_db": h.syncWriter.usersDBPath,
})
}
// --- Helpers ---
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
// ErrorResponse is a generic error response.
type ErrorResponse struct {
Error string `json:"error"`
}
// IsHTMLRequest checks if the client expects HTML (for HTMX routing).
func IsHTMLRequest(r *http.Request) bool {
accept := r.Header.Get("Accept")
return strings.Contains(accept, "text/html") || r.Header.Get("HX-Request") != ""
}