package handler import ( "fmt" "net/http" "path/filepath" "strings" "ci/internal/service" "github.com/gin-gonic/gin" ) // UploadHandler handles file upload endpoints. type UploadHandler struct { Svc *service.ProjectService } func NewUploadHandler(svc *service.ProjectService) *UploadHandler { return &UploadHandler{Svc: svc} } // UploadFiles handles source file upload for a project (zip/tar.gz). func (h *UploadHandler) UploadFiles(c *gin.Context) { id, err := parseID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"}) return } // Verify project exists and is upload mode project, err := h.Svc.GetProject(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } file, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "no file provided"}) return } // Validate file extension ext := strings.ToLower(filepath.Ext(file.Filename)) valid := false if ext == ".zip" { valid = true } if strings.HasSuffix(strings.ToLower(file.Filename), ".tar.gz") || strings.HasSuffix(strings.ToLower(file.Filename), ".tgz") { valid = true } if !valid { c.JSON(http.StatusBadRequest, gin.H{"error": "only .zip and .tar.gz files are supported"}) return } // Save uploaded file to temp location tmpPath := filepath.Join(h.Svc.Workspace.ProjectDir(project.ID), file.Filename) if err := c.SaveUploadedFile(file, tmpPath); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save file: %v", err)}) return } // Extract to src directory if err := h.Svc.Workspace.ExtractArchive(project.ID, tmpPath); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to extract: %v", err)}) return } c.JSON(http.StatusOK, gin.H{ "message": "file uploaded and extracted successfully", "filename": file.Filename, }) }