package handler import ( "context" "io" "strconv" "gcy_hpc_server/internal/model" "gcy_hpc_server/internal/server" "gcy_hpc_server/internal/service" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // uploadServiceProvider defines the interface for upload operations. type uploadServiceProvider interface { InitUpload(ctx context.Context, req model.InitUploadRequest) (interface{}, error) UploadChunk(ctx context.Context, sessionID int64, chunkIndex int, reader io.Reader, size int64) error CompleteUpload(ctx context.Context, sessionID int64) (*model.FileResponse, error) GetUploadStatus(ctx context.Context, sessionID int64) (*model.UploadSessionResponse, error) CancelUpload(ctx context.Context, sessionID int64) error } // UploadHandler handles HTTP requests for chunked file uploads. type UploadHandler struct { svc uploadServiceProvider logger *zap.Logger } // NewUploadHandler creates a new UploadHandler. func NewUploadHandler(svc *service.UploadService, logger *zap.Logger) *UploadHandler { return &UploadHandler{svc: svc, logger: logger} } // InitUpload initiates a new chunked upload session. // POST /api/v1/files/uploads func (h *UploadHandler) InitUpload(c *gin.Context) { var req model.InitUploadRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("invalid request body for init upload", zap.Error(err)) server.BadRequest(c, err.Error()) return } result, err := h.svc.InitUpload(c.Request.Context(), req) if err != nil { h.logger.Error("failed to init upload", zap.Error(err)) server.BadRequest(c, err.Error()) return } switch resp := result.(type) { case model.FileResponse: server.OK(c, resp) case model.UploadSessionResponse: server.Created(c, resp) default: server.Created(c, resp) } } // UploadChunk uploads a single chunk of an upload session. // PUT /api/v1/files/uploads/:id/chunks/:index func (h *UploadHandler) UploadChunk(c *gin.Context) { sessionID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { h.logger.Warn("invalid session id", zap.String("id", c.Param("id"))) server.BadRequest(c, "invalid session id") return } chunkIndex, err := strconv.Atoi(c.Param("index")) if err != nil { h.logger.Warn("invalid chunk index", zap.String("index", c.Param("index"))) server.BadRequest(c, "invalid chunk index") return } file, header, err := c.Request.FormFile("chunk") if err != nil { h.logger.Warn("missing chunk file in request", zap.Error(err)) server.BadRequest(c, "missing chunk file") return } defer file.Close() if err := h.svc.UploadChunk(c.Request.Context(), sessionID, chunkIndex, file, header.Size); err != nil { h.logger.Error("failed to upload chunk", zap.Int64("session_id", sessionID), zap.Int("chunk_index", chunkIndex), zap.Error(err)) server.BadRequest(c, err.Error()) return } server.OK(c, gin.H{"message": "chunk uploaded"}) } // CompleteUpload finalizes an upload session and assembles the file. // POST /api/v1/files/uploads/:id/complete func (h *UploadHandler) CompleteUpload(c *gin.Context) { sessionID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { h.logger.Warn("invalid session id for complete", zap.String("id", c.Param("id"))) server.BadRequest(c, "invalid session id") return } resp, err := h.svc.CompleteUpload(c.Request.Context(), sessionID) if err != nil { h.logger.Error("failed to complete upload", zap.Int64("session_id", sessionID), zap.Error(err)) server.InternalError(c, err.Error()) return } server.Created(c, resp) } // GetUploadStatus returns the current status of an upload session. // GET /api/v1/files/uploads/:id func (h *UploadHandler) GetUploadStatus(c *gin.Context) { sessionID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { h.logger.Warn("invalid session id for status", zap.String("id", c.Param("id"))) server.BadRequest(c, "invalid session id") return } resp, err := h.svc.GetUploadStatus(c.Request.Context(), sessionID) if err != nil { h.logger.Error("failed to get upload status", zap.Int64("session_id", sessionID), zap.Error(err)) server.InternalError(c, err.Error()) return } server.OK(c, resp) } // CancelUpload cancels and cleans up an upload session. // DELETE /api/v1/files/uploads/:id func (h *UploadHandler) CancelUpload(c *gin.Context) { sessionID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { h.logger.Warn("invalid session id for cancel", zap.String("id", c.Param("id"))) server.BadRequest(c, "invalid session id") return } if err := h.svc.CancelUpload(c.Request.Context(), sessionID); err != nil { h.logger.Error("failed to cancel upload", zap.Int64("session_id", sessionID), zap.Error(err)) server.InternalError(c, err.Error()) return } server.OK(c, gin.H{"message": "upload cancelled"}) }