feat: add admin panel with user, tag, and signup management

Phase 4 — Admin Panel:
- Admin dashboard with user/fave/pending-request counts
- User management: create with temp password, reset password,
  enable/disable accounts (prevents self-disable)
- Tag management: rename and delete tags
- Signup request management: approve (creates user with
  must-reset-password) and reject pending requests
- Site settings: site name, description, signup mode
  (open/requests/closed)
- All admin routes require both login and admin role
- SignupRequest model and full store (create, list pending,
  approve with user creation, reject)
- SetMustResetPassword method on UserStore for admin password resets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-29 16:09:30 +02:00
commit 13aec5be6e
11 changed files with 778 additions and 1 deletions

View file

@ -112,5 +112,22 @@ func (h *Handler) Routes() *http.ServeMux {
mux.Handle("POST /settings/avatar", requireLogin(http.HandlerFunc(h.handleAvatarPost)))
mux.Handle("POST /settings/password", requireLogin(http.HandlerFunc(h.handleSettingsPasswordPost)))
// Admin panel (requires admin role).
admin := func(hf http.HandlerFunc) http.Handler {
return requireLogin(middleware.RequireAdmin(http.HandlerFunc(hf)))
}
mux.Handle("GET /admin", admin(h.handleAdminDashboard))
mux.Handle("GET /admin/users", admin(h.handleAdminUsers))
mux.Handle("POST /admin/users", admin(h.handleAdminCreateUser))
mux.Handle("POST /admin/users/{id}/reset-password", admin(h.handleAdminResetPassword))
mux.Handle("POST /admin/users/{id}/toggle-disabled", admin(h.handleAdminToggleDisabled))
mux.Handle("GET /admin/tags", admin(h.handleAdminTags))
mux.Handle("POST /admin/tags/{id}/rename", admin(h.handleAdminRenameTag))
mux.Handle("POST /admin/tags/{id}/delete", admin(h.handleAdminDeleteTag))
mux.Handle("GET /admin/signup-requests", admin(h.handleAdminSignupRequests))
mux.Handle("POST /admin/signup-requests/{id}", admin(h.handleAdminSignupRequestAction))
mux.Handle("GET /admin/settings", admin(h.handleAdminSettingsGet))
mux.Handle("POST /admin/settings", admin(h.handleAdminSettingsPost))
return mux
}