From a3312ef6f84cc1d13e95596e6ce72e07da57d7ac Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Tue, 6 May 2025 21:50:06 +0100 Subject: [PATCH 1/8] add auth to /uploads --- .gitignore | 1 + internal/api/routes/upload.go | 18 ++++++++++++++++-- internal/auth/jwt.go | 6 +++--- internal/auth/types.go | 12 +++++++++++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index b0088e0..d17bf15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env tmp *.db +imgs \ No newline at end of file diff --git a/internal/api/routes/upload.go b/internal/api/routes/upload.go index baed591..d4950e7 100644 --- a/internal/api/routes/upload.go +++ b/internal/api/routes/upload.go @@ -4,22 +4,36 @@ import ( "path/filepath" "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "stereo.cat/backend/internal/auth" "stereo.cat/backend/internal/types" ) func RegisterUploadRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { - api.POST("/upload", func(c *gin.Context) { + 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 } - filePath := filepath.Join(cfg.ImagePath, file.Filename) + filePath := filepath.Join(cfg.ImagePath, uid, file.Filename) if err := c.SaveUploadedFile(file, filePath); err != nil { c.JSON(500, gin.H{"error": "failed to save file"}) return } + + c.JSON(200, gin.H{"message": "file uploaded successfully"}) }) api.GET("/:name", func(c *gin.Context) { diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go index 9e7aec2..1ea6d0f 100644 --- a/internal/auth/jwt.go +++ b/internal/auth/jwt.go @@ -11,9 +11,9 @@ import ( ) func GenerateJWT(key string, user User, expiryTimestamp uint64) (string, error) { - claims := jwt.MapClaims{ - "user": user, - "exp": expiryTimestamp, + claims := Claims{ + User: user, + Exp: expiryTimestamp, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) diff --git a/internal/auth/types.go b/internal/auth/types.go index a83b941..8627df7 100644 --- a/internal/auth/types.go +++ b/internal/auth/types.go @@ -1,6 +1,10 @@ package auth -import "time" +import ( + "time" + + "github.com/golang-jwt/jwt/v5" +) type TokenResponse struct { AccessToken string `json:"access_token"` @@ -28,3 +32,9 @@ type ExchangeCodeRequest struct { Code string `json:"code"` RedirectUri string `json:"redirect_uri"` } + +type Claims struct { + User User `json:"user"` + Exp uint64 `json:"exp"` + jwt.RegisteredClaims +} -- 2.47.2 From 93aec1336d2fa3c9576cba36f35aeb5bf19a2871 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Tue, 6 May 2025 22:20:30 +0100 Subject: [PATCH 2/8] add to db --- go.mod | 2 ++ go.sum | 4 ++++ internal/api/routes/upload.go | 23 +++++++++++++++++++++-- internal/types/types.go | 13 +++++++++++-- main.go | 2 +- 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index db43dc3..72ef5f2 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.4 // indirect @@ -29,6 +30,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lithammer/shortuuid/v4 v4.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index d3761d1..ce498a0 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI 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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -60,6 +62,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c= +github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= diff --git a/internal/api/routes/upload.go b/internal/api/routes/upload.go index d4950e7..e55ee52 100644 --- a/internal/api/routes/upload.go +++ b/internal/api/routes/upload.go @@ -1,7 +1,9 @@ package routes import ( + "os" "path/filepath" + "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" @@ -15,7 +17,6 @@ func RegisterUploadRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { user := claims["user"].(auth.User) uid := user.ID - if uid == "" { c.JSON(401, gin.H{"error": "unauthorized"}) return @@ -28,12 +29,30 @@ func RegisterUploadRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { } filePath := filepath.Join(cfg.ImagePath, uid, file.Filename) + + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + c.JSON(500, gin.H{"error": "failed to create directory"}) + return + } + if err := c.SaveUploadedFile(file, filePath); err != nil { c.JSON(500, gin.H{"error": "failed to save file"}) return } - c.JSON(200, gin.H{"message": "file uploaded successfully"}) + fileMeta := types.File{ + ID: uid + "_" + file.Filename, + Path: filePath, + Owner: uid, + CreatedAt: time.Now(), + } + + if err := cfg.Database.Create(&fileMeta).Error; err != nil { + c.JSON(500, gin.H{"error": "failed to save file metadata"}) + return + } + + c.JSON(200, gin.H{"message": "file uploaded successfully", "file_id": fileMeta.ID}) }) api.GET("/:name", func(c *gin.Context) { diff --git a/internal/types/types.go b/internal/types/types.go index c29a507..98dd231 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -1,6 +1,8 @@ package types import ( + "time" + "github.com/gin-gonic/gin" "gorm.io/gorm" "stereo.cat/backend/internal/auth/client" @@ -16,6 +18,13 @@ type StereoConfig struct { ImagePath string Router *gin.Engine Client client.Client - Database *gorm.DB - JWTSecret string + Database *gorm.DB + JWTSecret string +} + +type File struct { + ID string `gorm:"primaryKey"` + Path string `gorm:"not null;index"` + Owner string `gorm:"not null;index"` + CreatedAt time.Time `gorm:"autoCreateTime"` } diff --git a/main.go b/main.go index f8a4ae7..6059919 100644 --- a/main.go +++ b/main.go @@ -83,7 +83,7 @@ func main() { panic(errors.New("Invalid database type was specified.")) } - c.Database.AutoMigrate(&auth.User{}) + c.Database.AutoMigrate(&auth.User{}, &types.File{}) api.Register(&c) -- 2.47.2 From e16a4eae261533e86951b400e0a9f986de0dc83f Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:48:05 +0100 Subject: [PATCH 3/8] skibidi --- internal/api/api.go | 2 +- internal/api/routes/{upload.go => files.go} | 15 ++++++++++++++- main.go | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) rename internal/api/routes/{upload.go => files.go} (76%) diff --git a/internal/api/api.go b/internal/api/api.go index 7861d03..592bc7b 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -7,6 +7,6 @@ import ( func Register(cfg *types.StereoConfig) { api := cfg.Router.Group("/api") - routes.RegisterUploadRoutes(cfg, api) + routes.RegisterFileRoutes(cfg, api) routes.RegisterAuthRoutes(cfg, api) } diff --git a/internal/api/routes/upload.go b/internal/api/routes/files.go similarity index 76% rename from internal/api/routes/upload.go rename to internal/api/routes/files.go index e55ee52..0471632 100644 --- a/internal/api/routes/upload.go +++ b/internal/api/routes/files.go @@ -11,7 +11,7 @@ import ( "stereo.cat/backend/internal/types" ) -func RegisterUploadRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { +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) @@ -60,4 +60,17 @@ func RegisterUploadRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { path := filepath.Join(cfg.ImagePath, name) c.File(path) }) + + 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) + }) } diff --git a/main.go b/main.go index 6059919..54351ce 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,6 @@ func main() { c.Database.AutoMigrate(&auth.User{}, &types.File{}) api.Register(&c) - + fmt.Printf("Running on port %s\n", getEnv("PORT", "8080")) c.Router.Run() } -- 2.47.2 From e5265e35e4f400d538775d331fe78c132f3fc020 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:00:56 +0100 Subject: [PATCH 4/8] sharing base64 file data --- internal/api/routes/files.go | 17 +++++++++++++++++ internal/types/types.go | 1 + 2 files changed, 18 insertions(+) diff --git a/internal/api/routes/files.go b/internal/api/routes/files.go index 0471632..f90fbd6 100644 --- a/internal/api/routes/files.go +++ b/internal/api/routes/files.go @@ -1,6 +1,7 @@ package routes import ( + "encoding/base64" "os" "path/filepath" "time" @@ -40,11 +41,17 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { return } + // conver file variable to base64 string + b64, err := convertToBase64(filePath) + if err != nil { + } + fileMeta := types.File{ ID: uid + "_" + file.Filename, Path: filePath, Owner: uid, CreatedAt: time.Now(), + Base64: b64, } if err := cfg.Database.Create(&fileMeta).Error; err != nil { @@ -74,3 +81,13 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { c.JSON(200, files) }) } + +func convertToBase64(filePath string) (string, error) { + file, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + + b64 := base64.StdEncoding.EncodeToString(file) + return b64, nil +} diff --git a/internal/types/types.go b/internal/types/types.go index 98dd231..6120f08 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -27,4 +27,5 @@ type File struct { Path string `gorm:"not null;index"` Owner string `gorm:"not null;index"` CreatedAt time.Time `gorm:"autoCreateTime"` + Base64 string `gorm:"type:text"` } -- 2.47.2 From f20ad1475b938f65a16b6d6abb3024bd83f423b0 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:01:06 +0100 Subject: [PATCH 5/8] j --- internal/api/routes/files.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/api/routes/files.go b/internal/api/routes/files.go index f90fbd6..e5a1216 100644 --- a/internal/api/routes/files.go +++ b/internal/api/routes/files.go @@ -41,7 +41,6 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { return } - // conver file variable to base64 string b64, err := convertToBase64(filePath) if err != nil { } -- 2.47.2 From 8942778377ac3864ac325bed676ca6ffe6d2f189 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:10:51 +0100 Subject: [PATCH 6/8] fix file retrieval --- internal/api/routes/files.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/api/routes/files.go b/internal/api/routes/files.go index e5a1216..ee1d295 100644 --- a/internal/api/routes/files.go +++ b/internal/api/routes/files.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "os" "path/filepath" + "strings" "time" "github.com/gin-gonic/gin" @@ -63,7 +64,17 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { api.GET("/:name", func(c *gin.Context) { name := c.Param("name") - path := filepath.Join(cfg.ImagePath, name) + parts := strings.SplitN(name, "_", 2) + if len(parts) != 2 { + c.JSON(400, gin.H{"error": "invalid file name"}) + return + } + uid, filename := parts[0], parts[1] + path := filepath.Join(cfg.ImagePath, uid, filename) + if _, err := os.Stat(path); err != nil { + c.JSON(404, gin.H{"error": "file not found"}) + return + } c.File(path) }) -- 2.47.2 From 064ce7979da95d4129ccdb93966f900b504244d0 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:27:40 +0100 Subject: [PATCH 7/8] delete route --- internal/api/routes/files.go | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/internal/api/routes/files.go b/internal/api/routes/files.go index ee1d295..28accb4 100644 --- a/internal/api/routes/files.go +++ b/internal/api/routes/files.go @@ -62,6 +62,57 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { c.JSON(200, gin.H{"message": "file uploaded successfully", "file_id": fileMeta.ID}) }) + api.DELETE("/delete", 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 + } + + var response struct { + FileID string `json:"file_id" binding:"required"` + } + + if err := c.ShouldBindJSON(&response); err != nil { + c.JSON(400, gin.H{"error": "file_id is required"}) + return + } + + resfID := response.FileID + if resfID == "" { + c.JSON(400, gin.H{"error": "file_id cannot be empty"}) + return + } + + parts := strings.SplitN(resfID, "_", 2) + if len(parts) != 2 { + c.JSON(400, gin.H{"error": "invalid file_id format"}) + return + } + + fileID, filename := parts[0], parts[1] + if fileID != uid { + c.JSON(403, gin.H{"error": "you can only delete your own files"}) + return + } + + filePath := filepath.Join(cfg.ImagePath, uid, filename) + if err := os.Remove(filePath); err != nil { + c.JSON(500, gin.H{"error": "failed to delete file"}) + return + } + + if err := cfg.Database.Where("id = ?", resfID).Delete(&types.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("/:name", func(c *gin.Context) { name := c.Param("name") parts := strings.SplitN(name, "_", 2) -- 2.47.2 From 6e34203afde3c710b895785a1f1489f5aaf5de44 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:44:19 +0100 Subject: [PATCH 8/8] =?UTF-8?q?no=20more=20base=2064=20=F0=9F=98=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/api/routes/files.go | 18 ++++-------------- internal/types/types.go | 2 +- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/internal/api/routes/files.go b/internal/api/routes/files.go index 28accb4..db70537 100644 --- a/internal/api/routes/files.go +++ b/internal/api/routes/files.go @@ -1,7 +1,6 @@ package routes import ( - "encoding/base64" "os" "path/filepath" "strings" @@ -42,8 +41,9 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { return } - b64, err := convertToBase64(filePath) - if err != nil { + if file.Size <= 0 { + c.JSON(400, gin.H{"error": "file size must be greater than zero"}) + return } fileMeta := types.File{ @@ -51,7 +51,7 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { Path: filePath, Owner: uid, CreatedAt: time.Now(), - Base64: b64, + Size: file.Size, } if err := cfg.Database.Create(&fileMeta).Error; err != nil { @@ -142,13 +142,3 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { c.JSON(200, files) }) } - -func convertToBase64(filePath string) (string, error) { - file, err := os.ReadFile(filePath) - if err != nil { - return "", err - } - - b64 := base64.StdEncoding.EncodeToString(file) - return b64, nil -} diff --git a/internal/types/types.go b/internal/types/types.go index 6120f08..99e5186 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -26,6 +26,6 @@ type File struct { ID string `gorm:"primaryKey"` Path string `gorm:"not null;index"` Owner string `gorm:"not null;index"` + Size int64 `gorm:"not null;type:bigint"` CreatedAt time.Time `gorm:"autoCreateTime"` - Base64 string `gorm:"type:text"` } -- 2.47.2