package handlers import ( "errors" "fmt" "io" "net/http" "os" "path/filepath" "sheetless-server/database" "sheetless-server/models" "sheetless-server/utils" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" ) const uploadDir = "./uploads" func init() { // Create uploads directory if it doesn't exist if err := os.MkdirAll(uploadDir, 0755); err != nil { panic("Failed to create uploads directory: " + err.Error()) } } func UploadSheet(c *gin.Context) { // Get form data title := c.PostForm("title") if title == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Title is required"}) return } composerUUIDStr := c.PostForm("composer_uuid") if composerUUIDStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Composer UUID is required"}) return } composerUUID, err := uuid.Parse(composerUUIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid composer UUID"}) return } var composer models.Composer if err := database.DB.First(&composer, composerUUID).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Composer not found"}) return } description := c.PostForm("description") // Get uploaded file file, header, err := c.Request.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "File upload failed"}) return } defer file.Close() // Validate file type (should be PDF) if filepath.Ext(header.Filename) != ".pdf" { c.JSON(http.StatusBadRequest, gin.H{"error": "Only PDF files are allowed"}) return } // Generate unique filename filename := fmt.Sprintf("%d%s", time.Now().Unix(), filepath.Ext(header.Filename)) filePath := filepath.Join(uploadDir, filename) // Save file out, err := os.Create(filePath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"}) return } defer out.Close() _, err = io.Copy(out, file) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"}) return } // Get file size fileInfo, err := out.Stat() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get file info"}) return } fileHash, err := utils.FileHashFromUpload(out) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed calculating hash"}) return } uuid, err := GenerateNonexistentSheetUuid() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed generating sheet uuid"}) return } // Create database record sheet := models.Sheet{ Uuid: *uuid, Title: title, Description: description, FilePath: filePath, FileSize: fileInfo.Size(), FileHash: fileHash, ComposerId: composer.Uuid, CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := database.DB.Create(&sheet).Error; err != nil { // Clean up file if database insert fails os.Remove(filePath) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save sheet metadata"}) return } c.JSON(http.StatusCreated, gin.H{ "message": "Sheet uploaded successfully", "sheet": sheet, }) } func ListSheets(c *gin.Context) { var sheets []models.Sheet if err := database.DB.Preload("Composer").Find(&sheets).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch sheets"}) return } c.JSON(http.StatusOK, gin.H{"sheets": sheets}) } func DownloadSheet(c *gin.Context) { uuid, err := uuid.Parse(c.Param("uuid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid sheet uuid"}) return } var sheet models.Sheet if err := database.DB.First(&sheet, uuid).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Sheet not found"}) return } if _, err := os.Stat(sheet.FilePath); os.IsNotExist(err) { c.JSON(http.StatusNotFound, gin.H{"error": "File not found"}) return } c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(sheet.FilePath))) c.Header("Content-Type", "application/pdf") c.File(sheet.FilePath) } func GenerateNonexistentSheetUuid() (*uuid.UUID, error) { for i := 0; i < 10; i++ { uuid := uuid.New() var exists bool err := database.DB.Model(&models.Sheet{}). Select("count(*) > 0"). Where("uuid = ?", uuid). Find(&exists). Error if err != nil { return nil, err } if !exists { return &uuid, nil } } return nil, errors.New("Somehow unable to generate new uuid for sheet.") }