Files
dumbassDashboard/backend/api/files.go
2025-09-12 12:38:11 +02:00

244 lines
6.2 KiB
Go

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