package handlers import ( "database/sql" "encoding/json" "log/slog" "net/http" "os" "strconv" "person-home/internal/db" "person-home/internal/models" "person-home/internal/storage" ) type AdminHandler struct { DB *sql.DB UploadDir string } func (h *AdminHandler) List(w http.ResponseWriter, r *http.Request) { projects, err := db.ListProjects(h.DB, "", false, 0) if err != nil { writeError(w, http.StatusInternalServerError, "failed to list projects") return } if projects == nil { projects = []models.Project{} } writeJSON(w, http.StatusOK, projects) } func (h *AdminHandler) Create(w http.ResponseWriter, r *http.Request) { var req models.CreateProjectRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.Title == "" { writeError(w, http.StatusBadRequest, "title is required") return } if req.Tags == nil { req.Tags = []string{} } project, err := db.CreateProject(h.DB, &req) if err != nil { slog.Error("create project", "error", err) writeError(w, http.StatusInternalServerError, "failed to create project") return } writeJSON(w, http.StatusCreated, project) } func (h *AdminHandler) Update(w http.ResponseWriter, r *http.Request) { idStr := r.PathValue("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid id") return } var req models.UpdateProjectRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } project, err := db.UpdateProject(h.DB, id, &req) if err != nil { slog.Error("update project", "error", err) writeError(w, http.StatusInternalServerError, "failed to update project") return } if project == nil { writeError(w, http.StatusNotFound, "project not found") return } writeJSON(w, http.StatusOK, project) } func (h *AdminHandler) Delete(w http.ResponseWriter, r *http.Request) { idStr := r.PathValue("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid id") return } // Retrieve image path before deletion for cleanup project, _ := db.GetProject(h.DB, id) if project != nil && project.Image != "" { os.Remove(h.UploadDir + "/" + project.Image) } if err := db.DeleteProject(h.DB, id); err != nil { if err == sql.ErrNoRows { writeError(w, http.StatusNotFound, "project not found") return } slog.Error("delete project", "error", err) writeError(w, http.StatusInternalServerError, "failed to delete project") return } w.WriteHeader(http.StatusNoContent) } func (h *AdminHandler) Export(w http.ResponseWriter, r *http.Request) { projects, err := db.ListProjects(h.DB, "", false, 0) if err != nil { writeError(w, http.StatusInternalServerError, "failed to export projects") return } if projects == nil { projects = []models.Project{} } w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Disposition", "attachment; filename=projects.json") json.NewEncoder(w).Encode(projects) } func (h *AdminHandler) Upload(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeError(w, http.StatusMethodNotAllowed, "method not allowed") return } if err := r.ParseMultipartForm(10 << 20); err != nil { writeError(w, http.StatusBadRequest, "file too large or invalid form data") return } file, header, err := r.FormFile("image") if err != nil { writeError(w, http.StatusBadRequest, "missing image file") return } defer file.Close() filename, err := storage.SaveImage(file, header, h.UploadDir) if err != nil { writeError(w, http.StatusInternalServerError, "failed to save image") return } writeJSON(w, http.StatusOK, map[string]string{ "url": "/uploads/" + filename, "filename": filename, }) }