Files
geo/server/internal/tenant/transport/image_handler.go
T
root 27389164b0 feat: add image management functionality
- Implemented image folder repository with CRUD operations.
- Added image reference repository for managing image references to articles.
- Created image repository for handling image assets, including listing, inserting, updating, and deleting images.
- Introduced image usage repository to track storage usage and quotas for tenants.
- Added SQL queries for image assets, folders, references, and usage.
- Developed image handler for HTTP endpoints to manage images and folders.
- Created database migration scripts for image-related tables and structures.
2026-04-16 20:40:41 +08:00

250 lines
6.4 KiB
Go

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
}