package handlers import ( "encoding/json" "fmt" "io" "net/http" "strconv" "strings" "sync" "pdf-form-converter/db" "pdf-form-converter/llm" ) type AdminHandler struct { db *db.DB llm *llm.Client mu sync.Mutex } func NewAdminHandler(d *db.DB, l *llm.Client) *AdminHandler { return &AdminHandler{db: d, llm: l} } func (h *AdminHandler) Register(mux *http.ServeMux) { mux.HandleFunc("/admin", h.handleAdmin) mux.HandleFunc("/admin/", h.handleAdmin) } func (h *AdminHandler) handleAdmin(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/admin") path = strings.TrimPrefix(path, "/") if path == "" { http.ServeFile(w, r, "static/admin/index.html") return } if path == "api/forms" { switch r.Method { case http.MethodGet: h.handleListForms(w, r) case http.MethodPost: h.handleUploadPDF(w, r) default: jsonError(w, "method not allowed", http.StatusMethodNotAllowed) } return } if path == "api/convert" && r.Method == http.MethodPost { h.handleConvert(w, r) return } // /api/forms/:id and /api/forms/:id/pdf if strings.HasPrefix(path, "api/forms/") { rest := strings.TrimPrefix(path, "api/forms/") parts := strings.SplitN(rest, "/", 2) id, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { http.NotFound(w, r) return } if len(parts) == 1 { switch r.Method { case http.MethodGet: h.handleGetForm(w, r, id) case http.MethodPut: h.handleUpdateForm(w, r, id) case http.MethodDelete: h.handleDeleteForm(w, r, id) default: jsonError(w, "method not allowed", http.StatusMethodNotAllowed) } return } if len(parts) == 2 && parts[1] == "pdf" && r.Method == http.MethodGet { h.handleGetPDF(w, r, id) return } } http.NotFound(w, r) } func (h *AdminHandler) handleListForms(w http.ResponseWriter, r *http.Request) { forms, err := h.db.ListForms() if err != nil { jsonError(w, err.Error(), http.StatusInternalServerError) return } jsonResponse(w, forms) } func (h *AdminHandler) handleUploadPDF(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(50 << 20) if err != nil { jsonError(w, "file too large", http.StatusBadRequest) return } file, header, err := r.FormFile("pdf") if err != nil { jsonError(w, "no PDF file provided", http.StatusBadRequest) return } defer file.Close() pdfData, err := io.ReadAll(file) if err != nil { jsonError(w, "failed to read file", http.StatusInternalServerError) return } title := r.FormValue("title") if title == "" { title = header.Filename } form, err := h.db.CreateForm(title, header.Filename, pdfData) if err != nil { jsonError(w, err.Error(), http.StatusInternalServerError) return } jsonResponse(w, form) } func (h *AdminHandler) handleConvert(w http.ResponseWriter, r *http.Request) { var req struct { ID int64 `json:"id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonError(w, "invalid request body", http.StatusBadRequest) return } form, err := h.db.GetForm(req.ID) if err != nil { jsonError(w, "form not found", http.StatusNotFound) return } if h.llm == nil { jsonError(w, "LLM client not configured", http.StatusServiceUnavailable) return } h.mu.Lock() h.db.UpdateConversion(req.ID, "converting") h.mu.Unlock() go func() { markdown, err := h.llm.ConvertPDF(form.PDFData) h.mu.Lock() defer h.mu.Unlock() if err != nil { h.db.UpdateConversion(req.ID, "error: "+err.Error()) return } h.db.UpdateMarkdown(req.ID, markdown) h.db.UpdateConversion(req.ID, "done") }() jsonResponse(w, map[string]string{"status": "converting"}) } func (h *AdminHandler) handleGetForm(w http.ResponseWriter, r *http.Request, id int64) { form, err := h.db.GetForm(id) if err != nil { jsonError(w, "form not found", http.StatusNotFound) return } jsonResponse(w, form) } func (h *AdminHandler) handleUpdateForm(w http.ResponseWriter, r *http.Request, id int64) { var req struct { Title string `json:"title"` Markdown string `json:"markdown"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonError(w, "invalid request body", http.StatusBadRequest) return } if req.Title != "" { h.db.UpdateTitle(id, req.Title) } if req.Markdown != "" { h.db.UpdateMarkdown(id, req.Markdown) } form, err := h.db.GetForm(id) if err != nil { jsonError(w, "form not found", http.StatusNotFound) return } jsonResponse(w, form) } func (h *AdminHandler) handleDeleteForm(w http.ResponseWriter, r *http.Request, id int64) { if err := h.db.DeleteForm(id); err != nil { jsonError(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } func (h *AdminHandler) handleGetPDF(w http.ResponseWriter, r *http.Request, id int64) { form, err := h.db.GetForm(id) if err != nil { jsonError(w, "form not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/pdf") w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", form.PDFName)) w.Write(form.PDFData) } func jsonResponse(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(data) } func jsonError(w http.ResponseWriter, msg string, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(map[string]string{"error": msg}) }