package api import ( "fmt" "io" "net/http" "os" "path/filepath" "project-dashboard/api/utils" "project-dashboard/models" "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // UploadFile handles file uploads func UploadFile(c *gin.Context) { user, exists := c.Get("user") if !exists { utils.ErrorResponse(c, http.StatusUnauthorized, "User not authenticated") return } userObj := user.(*models.User) project, _ := c.Get("project") projectObj := project.(*models.Project) // Get task ID if provided taskIDStr := c.PostForm("task_id") var taskID *uint if taskIDStr != "" { if id, err := strconv.ParseUint(taskIDStr, 10, 32); err == nil { taskIDUint := uint(id) taskID = &taskIDUint } } // Get file from form file, header, err := c.Request.FormFile("file") if err != nil { utils.ErrorResponse(c, http.StatusBadRequest, "No file provided") return } defer file.Close() // Validate file size (10MB limit) const maxFileSize = 10 << 20 // 10MB if header.Size > maxFileSize { utils.ErrorResponse(c, http.StatusBadRequest, "File size too large (max 10MB)") return } // Generate unique filename ext := filepath.Ext(header.Filename) fileName := uuid.New().String() + ext // Create uploads directory if it doesn't exist uploadDir := "./uploads" if err := os.MkdirAll(uploadDir, 0755); err != nil { utils.ErrorResponse(c, http.StatusInternalServerError, "Failed to create upload directory") return } // Save file filePath := filepath.Join(uploadDir, fileName) dst, err := os.Create(filePath) if err != nil { utils.ErrorResponse(c, http.StatusInternalServerError, "Failed to create file") return } defer dst.Close() if _, err := io.Copy(dst, file); err != nil { utils.ErrorResponse(c, http.StatusInternalServerError, "Failed to save file") return } // Get MIME type mimeType := header.Header.Get("Content-Type") if mimeType == "" { mimeType = "application/octet-stream" } // Create file record fileUpload := models.FileUpload{ FileName: fileName, OriginalName: header.Filename, FilePath: filePath, FileSize: header.Size, MimeType: mimeType, UploadedByID: userObj.ID, ProjectID: &projectObj.ID, TaskID: taskID, } if err := models.GetDB().Create(&fileUpload).Error; err != nil { // Clean up file if database save fails os.Remove(filePath) utils.ErrorResponse(c, http.StatusInternalServerError, "Failed to save file record") return } // Load file with relationships models.GetDB(). Preload("UploadedBy"). Preload("Project"). Preload("Task"). First(&fileUpload, fileUpload.ID) utils.SuccessResponse(c, http.StatusCreated, "File uploaded successfully", fileUpload) } // GetFiles returns all files for a project func GetFiles(c *gin.Context) { project, exists := c.Get("project") if !exists { utils.ErrorResponse(c, http.StatusNotFound, "Project not found") return } projectObj := project.(*models.Project) var files []models.FileUpload if err := models.GetDB(). Preload("UploadedBy"). Preload("Project"). Preload("Task"). Where("project_id = ?", projectObj.ID). Order("created_at DESC"). Find(&files).Error; err != nil { utils.ErrorResponse(c, http.StatusInternalServerError, "Failed to fetch files") return } utils.SuccessResponse(c, http.StatusOK, "Files retrieved successfully", files) } // GetTaskFiles returns all files for a specific task func GetTaskFiles(c *gin.Context) { project, _ := c.Get("project") projectObj := project.(*models.Project) taskID := c.Param("task_id") if taskID == "" { utils.ErrorResponse(c, http.StatusBadRequest, "Task ID required") return } // Verify task exists and belongs to project var task models.Task if err := models.GetDB(). Where("id = ? AND project_id = ?", taskID, projectObj.ID). First(&task).Error; err != nil { utils.ErrorResponse(c, http.StatusNotFound, "Task not found") return } var files []models.FileUpload if err := models.GetDB(). Preload("UploadedBy"). Preload("Project"). Preload("Task"). Where("task_id = ?", taskID). Order("created_at DESC"). Find(&files).Error; err != nil { utils.ErrorResponse(c, http.StatusInternalServerError, "Failed to fetch task files") return } utils.SuccessResponse(c, http.StatusOK, "Task files retrieved successfully", files) } // DownloadFile serves a file for download func DownloadFile(c *gin.Context) { project, _ := c.Get("project") projectObj := project.(*models.Project) fileID := c.Param("file_id") if fileID == "" { utils.ErrorResponse(c, http.StatusBadRequest, "File ID required") return } var fileUpload models.FileUpload if err := models.GetDB(). Where("id = ? AND project_id = ?", fileID, projectObj.ID). First(&fileUpload).Error; err != nil { utils.ErrorResponse(c, http.StatusNotFound, "File not found") return } // Check if file exists on disk if _, err := os.Stat(fileUpload.FilePath); os.IsNotExist(err) { utils.ErrorResponse(c, http.StatusNotFound, "File not found on disk") return } // Set headers for file download c.Header("Content-Description", "File Transfer") c.Header("Content-Transfer-Encoding", "binary") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileUpload.OriginalName)) c.Header("Content-Type", "application/octet-stream") // Serve file c.File(fileUpload.FilePath) } // DeleteFile deletes a file func DeleteFile(c *gin.Context) { project, _ := c.Get("project") projectObj := project.(*models.Project) fileID := c.Param("file_id") if fileID == "" { utils.ErrorResponse(c, http.StatusBadRequest, "File ID required") return } var fileUpload models.FileUpload if err := models.GetDB(). Where("id = ? AND project_id = ?", fileID, projectObj.ID). First(&fileUpload).Error; err != nil { utils.ErrorResponse(c, http.StatusNotFound, "File not found") return } // Delete file from disk if err := os.Remove(fileUpload.FilePath); err != nil { // Log error but continue with database deletion fmt.Printf("Failed to delete file from disk: %v\n", err) } // Delete file record from database if err := models.GetDB().Delete(&fileUpload).Error; err != nil { utils.ErrorResponse(c, http.StatusInternalServerError, "Failed to delete file record") return } utils.SuccessResponse(c, http.StatusOK, "File deleted successfully", nil) }