27389164b0
- 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.
250 lines
6.4 KiB
Go
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
|
|
}
|