Files
hpc/internal/handler/upload_handler.go
dailz 2298e92516 feat(handler): add upload, file, and folder handlers with routes
Add UploadHandler (5 endpoints), FileHandler (4 endpoints), FolderHandler (4 endpoints) with Gin route registration in server.go.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-15 09:23:17 +08:00

155 lines
4.7 KiB
Go

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"})
}