package routes import ( "bytes" "io" "log" "strings" "time" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" "github.com/golang-jwt/jwt/v5" "github.com/h2non/filetype" "github.com/minio/minio-go/v7" "stereo.cat/backend/internal/auth" "stereo.cat/backend/internal/types" ) func intoReader(buf []byte) io.Reader { return io.NopCloser(bytes.NewBuffer(buf)) } func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { api.POST("/upload", auth.JwtMiddleware(cfg.JWTSecret), func(c *gin.Context) { claims := c.MustGet("claims").(jwt.MapClaims) user := claims["user"].(auth.User) uid := user.ID if uid == "" { c.JSON(401, gin.H{"error": "unauthorized"}) return } file, err := c.FormFile("file") if err != nil { c.JSON(400, gin.H{"error": "file is required"}) return } if file.Size <= 0 { c.JSON(400, gin.H{"error": "file size must be greater than zero"}) return } fileReader, err := file.Open() if err != nil { c.JSON(500, gin.H{"error": "couldn't open file"}) log.Println(err) return } buf, err := io.ReadAll(fileReader) if err != nil { c.JSON(500, gin.H{"error": "couldn't open file"}) log.Println(err) return } fileType, err := filetype.Match(buf) if err != nil { c.JSON(500, gin.H{"error": "couldn't open file"}) log.Println(err) return } fileMeta := types.File{ Owner: uid, Name: file.Filename, CreatedAt: time.Now(), Size: int64(len(buf)), Mime: fileType.MIME.Value, } if err := cfg.Database.Create(&fileMeta).Error; err != nil { c.JSON(500, gin.H{"error": "failed to save file metadata"}) return } _, err = cfg.MinioClient.PutObject(cfg.Context, cfg.Bucket, fileMeta.ID.String(), intoReader(buf), fileMeta.Size, minio.PutObjectOptions{ContentType: fileMeta.Mime}) if err != nil { c.JSON(500, gin.H{"error": "failed to upload file"}) log.Println(err) return } c.JSON(200, gin.H{"message": "file uploaded successfully", "id": fileMeta.ID.String()}) }) api.DELETE("/:id", auth.JwtMiddleware(cfg.JWTSecret), func(c *gin.Context) { claims := c.MustGet("claims").(jwt.MapClaims) user := claims["user"].(auth.User) uid := c.Param("uid") uid = strings.TrimSpace(uid) if uid == "" { c.JSON(400, gin.H{"error": "uid is required"}) return } if uid != user.ID { c.JSON(403, gin.H{"error": "you can only delete your own files"}) return } fileID, err := uuid.FromString(strings.TrimSpace(c.Param("id"))) if err != nil { c.JSON(404, gin.H{"error": "file not found"}) return } var file *types.File cfg.Database.First(&file, fileID) if file == nil { c.JSON(404, gin.H{"error": "file not found"}) return } if err := cfg.MinioClient.RemoveObject(cfg.Context, cfg.Bucket, fileID.String(), minio.RemoveObjectOptions{}); err != nil { c.JSON(500, gin.H{"error": "failed to delete file"}) return } if err := cfg.Database.Delete(&file).Error; err != nil { c.JSON(500, gin.H{"error": "failed to delete file metadata"}) return } c.JSON(200, gin.H{"message": "file deleted successfully"}) }) api.GET("/:id", func(c *gin.Context) { fileID := c.Param("id") fileID = strings.TrimSpace(fileID) var file *types.File id, err := uuid.FromString(fileID) if err != nil { c.JSON(500, gin.H{"error": "invalid file id"}) log.Println(err) return } cfg.Database.First(&file, id) if file == nil { c.JSON(500, gin.H{"error": "file does not exist"}) log.Println(err) return } object, err := cfg.MinioClient.GetObject(cfg.Context, cfg.Bucket, fileID, minio.GetObjectOptions{}) if err != nil { c.JSON(500, gin.H{"error": "failed to retrieve file"}) log.Println(err) return } c.DataFromReader(200, file.Size, file.Mime, object, nil) }) api.GET("/list", auth.JwtMiddleware(cfg.JWTSecret), func(c *gin.Context) { claims := c.MustGet("claims").(jwt.MapClaims) user := claims["user"].(auth.User) var files []types.File if err := cfg.Database.Where("owner = ?", user.ID).Find(&files).Error; err != nil { c.JSON(500, gin.H{"error": "failed to retrieve files"}) return } c.JSON(200, files) }) }