diff --git a/src/database/connection.go b/src/database/connection.go index 1fd4ee0..961c0e4 100644 --- a/src/database/connection.go +++ b/src/database/connection.go @@ -18,10 +18,10 @@ func InitDatabase() { } // Auto migrate the schema - err = DB.AutoMigrate(&models.User{}, &models.MusicSheet{}) + err = DB.AutoMigrate(&models.User{}, &models.Sheet{}) if err != nil { log.Fatal("Failed to migrate database:", err) } log.Println("Database connected and migrated successfully") -} \ No newline at end of file +} diff --git a/src/go.mod b/src/go.mod index 75d98d4..02eca8f 100644 --- a/src/go.mod +++ b/src/go.mod @@ -19,6 +19,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/src/go.sum b/src/go.sum index add629b..c4faa15 100644 --- a/src/go.sum +++ b/src/go.sum @@ -29,6 +29,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= diff --git a/src/handlers/auth.go b/src/handlers/auth.go index 6ed2e11..bedfbef 100644 --- a/src/handlers/auth.go +++ b/src/handlers/auth.go @@ -11,7 +11,7 @@ import ( "golang.org/x/crypto/bcrypt" ) -var jwtSecret = []byte("your-secret-key") // In production, use environment variable +var jwtSecret = []byte("your-secret-key") // TODO: In production, use environment variable type RegisterRequest struct { Username string `json:"username" binding:"required"` diff --git a/src/handlers/sheets.go b/src/handlers/sheets.go index eb04aad..75a5b19 100644 --- a/src/handlers/sheets.go +++ b/src/handlers/sheets.go @@ -1,6 +1,7 @@ package handlers import ( + "errors" "fmt" "io" "net/http" @@ -8,10 +9,12 @@ import ( "path/filepath" "sheetless-server/database" "sheetless-server/models" + "sheetless-server/utils" "strconv" "time" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) const uploadDir = "./uploads" @@ -79,14 +82,29 @@ func UploadSheet(c *gin.Context) { 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.MusicSheet{ + sheet := models.Sheet{ + Uuid: *uuid, Title: title, Composer: composer, Description: description, FilePath: filePath, FileSize: fileInfo.Size(), - UserID: userID.(uint), + FileHash: fileHash, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } if err := database.DB.Create(&sheet).Error; err != nil { @@ -103,7 +121,7 @@ func UploadSheet(c *gin.Context) { } func ListSheets(c *gin.Context) { - var sheets []models.MusicSheet + var sheets []models.Sheet query := database.DB.Preload("User") @@ -131,7 +149,7 @@ func DownloadSheet(c *gin.Context) { return } - var sheet models.MusicSheet + var sheet models.Sheet if err := database.DB.First(&sheet, uint(id)).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Sheet not found"}) return @@ -147,4 +165,26 @@ func DownloadSheet(c *gin.Context) { c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(sheet.FilePath))) c.Header("Content-Type", "application/pdf") c.File(sheet.FilePath) -} \ No newline at end of file +} + +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.") +} diff --git a/src/models/sheet.go b/src/models/sheet.go index a9b7936..3aee692 100644 --- a/src/models/sheet.go +++ b/src/models/sheet.go @@ -3,19 +3,20 @@ package models import ( "time" + "github.com/google/uuid" "gorm.io/gorm" ) -type MusicSheet struct { - ID uint `json:"id" gorm:"primaryKey"` +type Sheet struct { + Uuid uuid.UUID `json:"uuid" gorm:"primaryKey"` Title string `json:"title" gorm:"not null"` Composer string `json:"composer"` Description string `json:"description"` FilePath string `json:"file_path" gorm:"not null"` FileSize int64 `json:"file_size"` - UserID uint `json:"user_id"` + FileHash uint64 `json:"file_hash"` User User `json:"user" gorm:"foreignKey:UserID"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` -} \ No newline at end of file +} diff --git a/src/routes/routes.go b/src/routes/routes.go index fa9b0e0..a448b1d 100644 --- a/src/routes/routes.go +++ b/src/routes/routes.go @@ -22,8 +22,8 @@ func SetupRoutes(r *gin.Engine) { sheets := api.Group("/sheets") { sheets.POST("/upload", handlers.UploadSheet) - sheets.GET("", handlers.ListSheets) - sheets.GET("/download/:id", handlers.DownloadSheet) + sheets.GET("/list", handlers.ListSheets) + sheets.GET("/get/:id", handlers.DownloadSheet) } } } diff --git a/src/sheetless-server b/src/sheetless-server new file mode 100755 index 0000000..47b5677 Binary files /dev/null and b/src/sheetless-server differ diff --git a/src/utils/filehash.go b/src/utils/filehash.go new file mode 100644 index 0000000..d3ea5fd --- /dev/null +++ b/src/utils/filehash.go @@ -0,0 +1,32 @@ +package utils + +import ( + "hash/fnv" + "io" + "mime/multipart" + "os" +) + +func FileHashFromUpload(file multipart.File) (uint64, error) { + h := fnv.New64a() + if _, err := io.Copy(h, file); err != nil { + return 0, err + } + + return h.Sum64(), nil +} + +func FileHash(path string) (uint64, error) { + f, err := os.Open(path) + if err != nil { + return 0, err + } + defer f.Close() + + h := fnv.New64a() + if _, err := io.Copy(h, f); err != nil { + return 0, err + } + + return h.Sum64(), nil +}