package transport import ( "io" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/geo-platform/tenant-api/internal/bootstrap" "github.com/geo-platform/tenant-api/internal/shared/response" "github.com/geo-platform/tenant-api/internal/tenant/app" ) type ImageHandler struct { imageSvc *app.ImageService folderSvc *app.ImageFolderService assets *AssetHandler } func NewImageHandler(a *bootstrap.App) *ImageHandler { return &ImageHandler{ imageSvc: app.NewImageService(a.DB, a.ObjectStorage, a.RabbitMQ, a.Logger), folderSvc: app.NewImageFolderService(a.DB, a.ObjectStorage, a.RabbitMQ, a.Logger), assets: NewAssetHandler(a), } } func (h *ImageHandler) ListFolders(c *gin.Context) { folders, err := h.folderSvc.List(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, folders) } func (h *ImageHandler) CreateFolder(c *gin.Context) { var body struct { Name string `json:"name"` } if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_request", "invalid request body")) return } id, err := h.folderSvc.Create(c.Request.Context(), body.Name) if err != nil { response.Error(c, err) return } response.SuccessWithStatus(c, http.StatusCreated, gin.H{"id": id}) } func (h *ImageHandler) UpdateFolder(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_id", "invalid folder id")) return } var body struct { Name string `json:"name"` } if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_request", "invalid request body")) return } if err := h.folderSvc.Update(c.Request.Context(), id, body.Name); err != nil { response.Error(c, err) return } response.Success(c, nil) } func (h *ImageHandler) FolderDeletePreview(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_id", "invalid folder id")) return } preview, err := h.folderSvc.DeletePreview(c.Request.Context(), id) if err != nil { response.Error(c, err) return } response.Success(c, preview) } func (h *ImageHandler) DeleteFolder(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_id", "invalid folder id")) return } if err := h.folderSvc.Delete(c.Request.Context(), id, c.Query("force") == "1"); err != nil { response.Error(c, err) return } response.Success(c, nil) } func (h *ImageHandler) ListImages(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) folderID, err := parseImageFolderFilter(c.Query("folder_id")) if err != nil { response.Error(c, err) return } var query *string if q := strings.TrimSpace(c.Query("q")); q != "" { query = &q } result, err := h.imageSvc.List(c.Request.Context(), folderID, query, page, pageSize, h.assets.BuildArticleImageURL) if err != nil { response.Error(c, err) return } response.Success(c, result) } func (h *ImageHandler) UploadImage(c *gin.Context) { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 11*1024*1024) file, header, err := c.Request.FormFile("file") if err != nil { if strings.Contains(err.Error(), "too large") { response.Error(c, response.ErrBadRequest(40011, "image_too_large", "image size cannot exceed 10 MB")) return } response.Error(c, response.ErrBadRequest(40000, "missing_file", "file is required")) return } defer file.Close() content, err := io.ReadAll(file) if err != nil { response.Error(c, response.ErrBadRequest(40000, "read_file_failed", "failed to read file")) return } folderID, err := parseAssignableFolderID(c.PostForm("folder_id")) if err != nil { response.Error(c, err) return } result, err := h.imageSvc.Upload(c.Request.Context(), header.Filename, content, folderID, h.assets.BuildArticleImageURL) if err != nil { response.Error(c, err) return } response.SuccessWithStatus(c, http.StatusCreated, result) } func (h *ImageHandler) UpdateImage(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_id", "invalid image id")) return } var body app.ImageUpdateRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_request", "invalid request body")) return } if err := h.imageSvc.Update(c.Request.Context(), id, body); err != nil { response.Error(c, err) return } response.Success(c, nil) } func (h *ImageHandler) GetImageReferences(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_id", "invalid image id")) return } refs, err := h.imageSvc.GetReferences(c.Request.Context(), id) if err != nil { response.Error(c, err) return } response.Success(c, refs) } func (h *ImageHandler) DeleteImage(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40000, "invalid_id", "invalid image id")) return } if err := h.imageSvc.Delete(c.Request.Context(), id, c.Query("force") == "1"); err != nil { response.Error(c, err) return } response.Success(c, nil) } func (h *ImageHandler) StorageUsage(c *gin.Context) { usage, err := h.imageSvc.GetStorageUsage(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, usage) } func parseImageFolderFilter(raw string) (*int64, error) { raw = strings.TrimSpace(raw) if raw == "" { return nil, nil } if raw == "null" || raw == "uncategorized" { uncategorized := int64(0) return &uncategorized, nil } value, err := strconv.ParseInt(raw, 10, 64) if err != nil || value <= 0 { return nil, response.ErrBadRequest(40003, "invalid_folder_id", "folder_id must be a positive integer") } return &value, nil } func parseAssignableFolderID(raw string) (*int64, error) { raw = strings.TrimSpace(raw) if raw == "" || raw == "null" || raw == "uncategorized" { return nil, nil } value, err := strconv.ParseInt(raw, 10, 64) if err != nil || value <= 0 { return nil, response.ErrBadRequest(40003, "invalid_folder_id", "folder_id must be a positive integer") } return &value, nil }