/* Copyright (C) 2025 hexlocation (hex@iwakura.rip) & grngxd (grng@iwakura.rip) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package routes import ( "bytes" "io" "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/auth/token" "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", token.JwtMiddleware(cfg.JWTSecret), func(c *gin.Context) { claims := c.MustGet("claims").(jwt.MapClaims) user := claims["user"].(auth.User) uid := user.ID if uid == "" { types.ErrorInvalidParams.Throw(c, nil) return } file, err := c.FormFile("file") if err != nil { types.ErrorInvalidParams.Throw(c, err) return } if file.Size <= 0 { types.ErrorInvalidParams.Throw(c, nil) return } fileReader, err := file.Open() if err != nil { types.ErrorReaderOpen.Throw(c, err) return } buf, err := io.ReadAll(fileReader) if err != nil { types.ErrorReaderOpen.Throw(c, err) return } if !filetype.IsImage(buf) { types.ErrorInvalidFile.Throw(c, nil) return } fileType, err := filetype.Match(buf) if err != nil { types.ErrorReaderOpen.Throw(c, 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 { types.ErrorDatabase.Throw(c, err) return } _, err = cfg.MinioClient.PutObject(cfg.Context, cfg.Bucket, fileMeta.ID.String(), intoReader(buf), fileMeta.Size, minio.PutObjectOptions{ContentType: fileMeta.Mime}) if err != nil { types.ErrorS3.Throw(c, err) return } c.JSON(200, gin.H{"message": "file uploaded successfully", "id": fileMeta.ID.String()}) }) api.DELETE("/:id", token.JwtMiddleware(cfg.JWTSecret), func(c *gin.Context) { claims := c.MustGet("claims").(jwt.MapClaims) user := claims["user"].(auth.User) fileID, err := uuid.FromString(strings.TrimSpace(c.Param("id"))) if err != nil { types.ErrorInvalidFile.Throw(c, err) return } var file *types.File err = cfg.Database.First(&file, fileID).Error if err != nil { types.ErrorFileNotFound.Throw(c, err) return } if file.Owner != user.ID { types.ErrorUnauthorized.Throw(c, nil) return } if err := cfg.MinioClient.RemoveObject(cfg.Context, cfg.Bucket, fileID.String(), minio.RemoveObjectOptions{}); err != nil { types.ErrorS3.Throw(c, err) return } if err := cfg.Database.Delete(&file).Error; err != nil { types.ErrorDatabase.Throw(c, err) 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 { types.ErrorInvalidFile.Throw(c, err) return } cfg.Database.First(&file, id) if file == nil { types.ErrorFileNotFound.Throw(c, nil) return } object, err := cfg.MinioClient.GetObject(cfg.Context, cfg.Bucket, fileID, minio.GetObjectOptions{}) if err != nil { types.ErrorS3.Throw(c, err) return } if _, err := object.Stat(); err != nil { types.ErrorFileNotFound.Throw(c, err) return } c.DataFromReader(200, file.Size, file.Mime, object, nil) }) api.GET("/list", token.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 { types.ErrorDatabase.Throw(c, err) return } c.JSON(200, files) }) }