|
|
|
|
@@ -111,6 +111,9 @@ type Config struct {
|
|
|
|
|
// Version and Commit are embedded in /health and /api/version responses.
|
|
|
|
|
Version string
|
|
|
|
|
Commit string
|
|
|
|
|
// AdminToken is the bearer token required for all /api/admin/* endpoints.
|
|
|
|
|
// When empty a startup warning is logged and admin routes are unprotected.
|
|
|
|
|
AdminToken string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Server is the HTTP API server.
|
|
|
|
|
@@ -137,9 +140,30 @@ func New(cfg Config, deps Dependencies) *Server {
|
|
|
|
|
return &Server{cfg: cfg, deps: deps}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// requireAdmin returns a handler that enforces Bearer token authentication.
|
|
|
|
|
// When AdminToken is empty all requests are allowed through (with a warning logged
|
|
|
|
|
// once at startup via ListenAndServe).
|
|
|
|
|
func (s *Server) requireAdmin(next http.HandlerFunc) http.HandlerFunc {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if s.cfg.AdminToken == "" {
|
|
|
|
|
next(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if r.Header.Get("Authorization") != "Bearer "+s.cfg.AdminToken {
|
|
|
|
|
jsonError(w, http.StatusUnauthorized, "unauthorized")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
next(w, r)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListenAndServe registers all routes and starts the HTTP server.
|
|
|
|
|
// It blocks until ctx is cancelled, then performs a graceful shutdown.
|
|
|
|
|
func (s *Server) ListenAndServe(ctx context.Context) error {
|
|
|
|
|
if s.cfg.AdminToken == "" {
|
|
|
|
|
s.deps.Log.Warn("backend: BACKEND_ADMIN_TOKEN is not set — /api/admin/* endpoints are unprotected")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
|
|
|
|
|
// Health / version
|
|
|
|
|
@@ -204,68 +228,73 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
|
|
|
|
|
mux.HandleFunc("GET /api/translation/status/{slug}/{n}", s.handleTranslationStatus)
|
|
|
|
|
mux.HandleFunc("GET /api/translation/{slug}/{n}", s.handleTranslationRead)
|
|
|
|
|
|
|
|
|
|
// admin is a shorthand that wraps every /api/admin/* handler with bearer-token auth.
|
|
|
|
|
admin := func(pattern string, h http.HandlerFunc) {
|
|
|
|
|
mux.HandleFunc(pattern, s.requireAdmin(h))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Admin translation endpoints
|
|
|
|
|
mux.HandleFunc("GET /api/admin/translation/jobs", s.handleAdminTranslationJobs)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/translation/bulk", s.handleAdminTranslationBulk)
|
|
|
|
|
admin("GET /api/admin/translation/jobs", s.handleAdminTranslationJobs)
|
|
|
|
|
admin("POST /api/admin/translation/bulk", s.handleAdminTranslationBulk)
|
|
|
|
|
|
|
|
|
|
// Admin audio endpoints
|
|
|
|
|
mux.HandleFunc("GET /api/admin/audio/jobs", s.handleAdminAudioJobs)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/audio/bulk", s.handleAdminAudioBulk)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/audio/cancel-bulk", s.handleAdminAudioCancelBulk)
|
|
|
|
|
admin("GET /api/admin/audio/jobs", s.handleAdminAudioJobs)
|
|
|
|
|
admin("POST /api/admin/audio/bulk", s.handleAdminAudioBulk)
|
|
|
|
|
admin("POST /api/admin/audio/cancel-bulk", s.handleAdminAudioCancelBulk)
|
|
|
|
|
|
|
|
|
|
// Admin image generation endpoints
|
|
|
|
|
mux.HandleFunc("GET /api/admin/image-gen/models", s.handleAdminImageGenModels)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/image-gen", s.handleAdminImageGen)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/image-gen/async", s.handleAdminImageGenAsync)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/image-gen/save-cover", s.handleAdminImageGenSaveCover)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/image-gen/save-chapter-image", s.handleAdminImageGenSaveChapterImage)
|
|
|
|
|
admin("GET /api/admin/image-gen/models", s.handleAdminImageGenModels)
|
|
|
|
|
admin("POST /api/admin/image-gen", s.handleAdminImageGen)
|
|
|
|
|
admin("POST /api/admin/image-gen/async", s.handleAdminImageGenAsync)
|
|
|
|
|
admin("POST /api/admin/image-gen/save-cover", s.handleAdminImageGenSaveCover)
|
|
|
|
|
admin("POST /api/admin/image-gen/save-chapter-image", s.handleAdminImageGenSaveChapterImage)
|
|
|
|
|
|
|
|
|
|
// Chapter image serving
|
|
|
|
|
mux.HandleFunc("GET /api/chapter-image/{domain}/{slug}/{n}", s.handleGetChapterImage)
|
|
|
|
|
mux.HandleFunc("HEAD /api/chapter-image/{domain}/{slug}/{n}", s.handleHeadChapterImage)
|
|
|
|
|
|
|
|
|
|
// Admin text generation endpoints (chapter names + book description)
|
|
|
|
|
mux.HandleFunc("GET /api/admin/text-gen/models", s.handleAdminTextGenModels)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/chapter-names", s.handleAdminTextGenChapterNames)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/chapter-names/async", s.handleAdminTextGenChapterNamesAsync)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/chapter-names/apply", s.handleAdminTextGenApplyChapterNames)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/description", s.handleAdminTextGenDescription)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/description/async", s.handleAdminTextGenDescriptionAsync)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/description/apply", s.handleAdminTextGenApplyDescription)
|
|
|
|
|
admin("GET /api/admin/text-gen/models", s.handleAdminTextGenModels)
|
|
|
|
|
admin("POST /api/admin/text-gen/chapter-names", s.handleAdminTextGenChapterNames)
|
|
|
|
|
admin("POST /api/admin/text-gen/chapter-names/async", s.handleAdminTextGenChapterNamesAsync)
|
|
|
|
|
admin("POST /api/admin/text-gen/chapter-names/apply", s.handleAdminTextGenApplyChapterNames)
|
|
|
|
|
admin("POST /api/admin/text-gen/description", s.handleAdminTextGenDescription)
|
|
|
|
|
admin("POST /api/admin/text-gen/description/async", s.handleAdminTextGenDescriptionAsync)
|
|
|
|
|
admin("POST /api/admin/text-gen/description/apply", s.handleAdminTextGenApplyDescription)
|
|
|
|
|
|
|
|
|
|
// Admin catalogue enrichment endpoints
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/tagline", s.handleAdminTextGenTagline)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/genres", s.handleAdminTextGenGenres)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/genres/apply", s.handleAdminTextGenApplyGenres)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/content-warnings", s.handleAdminTextGenContentWarnings)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/text-gen/quality-score", s.handleAdminTextGenQualityScore)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/catalogue/batch-covers", s.handleAdminBatchCovers)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/catalogue/batch-covers/cancel", s.handleAdminBatchCoversCancel)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/catalogue/refresh-metadata/{slug}", s.handleAdminRefreshMetadata)
|
|
|
|
|
admin("POST /api/admin/text-gen/tagline", s.handleAdminTextGenTagline)
|
|
|
|
|
admin("POST /api/admin/text-gen/genres", s.handleAdminTextGenGenres)
|
|
|
|
|
admin("POST /api/admin/text-gen/genres/apply", s.handleAdminTextGenApplyGenres)
|
|
|
|
|
admin("POST /api/admin/text-gen/content-warnings", s.handleAdminTextGenContentWarnings)
|
|
|
|
|
admin("POST /api/admin/text-gen/quality-score", s.handleAdminTextGenQualityScore)
|
|
|
|
|
admin("POST /api/admin/catalogue/batch-covers", s.handleAdminBatchCovers)
|
|
|
|
|
admin("POST /api/admin/catalogue/batch-covers/cancel", s.handleAdminBatchCoversCancel)
|
|
|
|
|
admin("POST /api/admin/catalogue/refresh-metadata/{slug}", s.handleAdminRefreshMetadata)
|
|
|
|
|
|
|
|
|
|
// Admin AI job tracking endpoints
|
|
|
|
|
mux.HandleFunc("GET /api/admin/ai-jobs", s.handleAdminListAIJobs)
|
|
|
|
|
mux.HandleFunc("GET /api/admin/ai-jobs/{id}", s.handleAdminGetAIJob)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/ai-jobs/{id}/cancel", s.handleAdminCancelAIJob)
|
|
|
|
|
admin("GET /api/admin/ai-jobs", s.handleAdminListAIJobs)
|
|
|
|
|
admin("GET /api/admin/ai-jobs/{id}", s.handleAdminGetAIJob)
|
|
|
|
|
admin("POST /api/admin/ai-jobs/{id}/cancel", s.handleAdminCancelAIJob)
|
|
|
|
|
|
|
|
|
|
// Auto-prompt generation from book/chapter content
|
|
|
|
|
mux.HandleFunc("POST /api/admin/image-gen/auto-prompt", s.handleAdminImageGenAutoPrompt)
|
|
|
|
|
admin("POST /api/admin/image-gen/auto-prompt", s.handleAdminImageGenAutoPrompt)
|
|
|
|
|
|
|
|
|
|
// Admin data repair endpoints
|
|
|
|
|
mux.HandleFunc("POST /api/admin/dedup-chapters/{slug}", s.handleDedupChapters)
|
|
|
|
|
admin("POST /api/admin/dedup-chapters/{slug}", s.handleDedupChapters)
|
|
|
|
|
|
|
|
|
|
// Admin book management (soft-delete / hard-delete)
|
|
|
|
|
mux.HandleFunc("PATCH /api/admin/books/{slug}/archive", s.handleAdminArchiveBook)
|
|
|
|
|
mux.HandleFunc("PATCH /api/admin/books/{slug}/unarchive", s.handleAdminUnarchiveBook)
|
|
|
|
|
mux.HandleFunc("DELETE /api/admin/books/{slug}", s.handleAdminDeleteBook)
|
|
|
|
|
admin("PATCH /api/admin/books/{slug}/archive", s.handleAdminArchiveBook)
|
|
|
|
|
admin("PATCH /api/admin/books/{slug}/unarchive", s.handleAdminUnarchiveBook)
|
|
|
|
|
admin("DELETE /api/admin/books/{slug}", s.handleAdminDeleteBook)
|
|
|
|
|
|
|
|
|
|
// Admin chapter split (imported books)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/books/{slug}/split-chapters", s.handleAdminSplitChapters)
|
|
|
|
|
admin("POST /api/admin/books/{slug}/split-chapters", s.handleAdminSplitChapters)
|
|
|
|
|
|
|
|
|
|
// Import (PDF/EPUB)
|
|
|
|
|
mux.HandleFunc("POST /api/admin/import", s.handleAdminImport)
|
|
|
|
|
mux.HandleFunc("GET /api/admin/import", s.handleAdminImportList)
|
|
|
|
|
mux.HandleFunc("GET /api/admin/import/{id}", s.handleAdminImportStatus)
|
|
|
|
|
admin("POST /api/admin/import", s.handleAdminImport)
|
|
|
|
|
admin("GET /api/admin/import", s.handleAdminImportList)
|
|
|
|
|
admin("GET /api/admin/import/{id}", s.handleAdminImportStatus)
|
|
|
|
|
|
|
|
|
|
// Notifications
|
|
|
|
|
mux.HandleFunc("GET /api/notifications", s.handleListNotifications)
|
|
|
|
|
|