package handlers import ( "context" "net/http" "strconv" "time" "github.com/flatrender/render-svc/internal/db" "github.com/flatrender/render-svc/internal/middleware" "github.com/flatrender/render-svc/internal/models" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/minio/minio-go/v7" ) type ExportHandler struct { store *db.Store minio *minio.Client bucket string } func NewExportHandler(store *db.Store, mc *minio.Client, bucket string) *ExportHandler { return &ExportHandler{store: store, minio: mc, bucket: bucket} } // GET /v1/exports func (h *ExportHandler) List(c *gin.Context) { userID := middleware.GetUserID(c) page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) if page < 1 { page = 1 } exports, total, err := h.store.ListExports(c.Request.Context(), userID, page, 20) if err != nil { c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: err.Error()}) return } if exports == nil { exports = []*models.Export{} } c.JSON(http.StatusOK, models.PagedResponse[*models.Export]{ Data: exports, Meta: models.PaginationMeta{ Page: page, PageSize: 20, Total: total, HasMore: int64(page*20) < total, }, }) } // GET /v1/exports/:export_id func (h *ExportHandler) Get(c *gin.Context) { userID := middleware.GetUserID(c) exportID, err := uuid.Parse(c.Param("export_id")) if err != nil { c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid export_id"}) return } exp, err := h.store.GetExportByID(c.Request.Context(), exportID, userID) if err != nil { c.JSON(http.StatusNotFound, models.APIError{Code: "not_found", Message: err.Error()}) return } files, _ := h.store.ListExportFiles(c.Request.Context(), exportID) if files == nil { files = []*models.ExportFile{} } c.JSON(http.StatusOK, gin.H{ "id": exp.ID, "saved_project_id": exp.SavedProjectID, "path": exp.Path, "image": exp.Image, "size_bytes": exp.SizeBytes, "duration_sec": exp.DurationSec, "width": exp.Width, "height": exp.Height, "file_extension": exp.FileExtension, "file_type": exp.FileType, "render_quality": exp.RenderQuality, "create_type": exp.CreateType, "produce_date": exp.ProduceDate, "auto_delete_date": exp.AutoDeleteDate, "files": files, }) } // DELETE /v1/exports/:export_id func (h *ExportHandler) Delete(c *gin.Context) { userID := middleware.GetUserID(c) exportID, err := uuid.Parse(c.Param("export_id")) if err != nil { c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid export_id"}) return } if err := h.store.SoftDeleteExport(c.Request.Context(), exportID, userID); err != nil { c.JSON(http.StatusNotFound, models.APIError{Code: "not_found", Message: err.Error()}) return } c.Status(http.StatusNoContent) } // POST /v1/exports/:export_id/extend func (h *ExportHandler) Extend(c *gin.Context) { userID := middleware.GetUserID(c) exportID, err := uuid.Parse(c.Param("export_id")) if err != nil { c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid export_id"}) return } var body struct { Days int `json:"days" binding:"required,min=1,max=365"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: err.Error()}) return } newDate, err := h.store.ExtendExportDeleteDate(c.Request.Context(), exportID, userID, body.Days) if err != nil { c.JSON(http.StatusNotFound, models.APIError{Code: "not_found", Message: err.Error()}) return } c.JSON(http.StatusOK, gin.H{"new_auto_delete_date": newDate}) } // GET /v1/exports/:export_id/download-url func (h *ExportHandler) DownloadURL(c *gin.Context) { userID := middleware.GetUserID(c) exportID, err := uuid.Parse(c.Param("export_id")) if err != nil { c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid export_id"}) return } exp, err := h.store.GetExportByID(c.Request.Context(), exportID, userID) if err != nil { c.JSON(http.StatusNotFound, models.APIError{Code: "not_found", Message: err.Error()}) return } // Generate presigned URL (15 min TTL) expiry := 15 * time.Minute url, err := h.minio.PresignedGetObject(context.Background(), h.bucket, exp.Path, expiry, nil) if err != nil { c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: "could not generate download URL"}) return } c.JSON(http.StatusOK, gin.H{ "url": url.String(), "expires_at": time.Now().Add(expiry), }) }