diff --git a/.gitignore b/.gitignore index d17bf15..b0088e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .env tmp *.db -imgs \ No newline at end of file diff --git a/go.mod b/go.mod index 72ef5f2..db43dc3 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ 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 @@ -30,7 +29,6 @@ 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 ce498a0..d3761d1 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,6 @@ 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= @@ -62,8 +60,6 @@ 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/api.go b/internal/api/api.go index 592bc7b..7861d03 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.RegisterFileRoutes(cfg, api) + routes.RegisterUploadRoutes(cfg, api) routes.RegisterAuthRoutes(cfg, api) } diff --git a/internal/api/routes/files.go b/internal/api/routes/files.go deleted file mode 100644 index 28accb4..0000000 --- a/internal/api/routes/files.go +++ /dev/null @@ -1,154 +0,0 @@ -package routes - -import ( - "encoding/base64" - "os" - "path/filepath" - "strings" - "time" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" - "stereo.cat/backend/internal/auth" - "stereo.cat/backend/internal/types" -) - -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 - } - - 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 - } - - 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 { - 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.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) - 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) - }) - - 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) - }) -} - -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/api/routes/upload.go b/internal/api/routes/upload.go new file mode 100644 index 0000000..baed591 --- /dev/null +++ b/internal/api/routes/upload.go @@ -0,0 +1,30 @@ +package routes + +import ( + "path/filepath" + + "github.com/gin-gonic/gin" + "stereo.cat/backend/internal/types" +) + +func RegisterUploadRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) { + api.POST("/upload", func(c *gin.Context) { + 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) + if err := c.SaveUploadedFile(file, filePath); err != nil { + c.JSON(500, gin.H{"error": "failed to save file"}) + return + } + }) + + api.GET("/:name", func(c *gin.Context) { + name := c.Param("name") + path := filepath.Join(cfg.ImagePath, name) + c.File(path) + }) +} diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go index 1ea6d0f..9e7aec2 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 := Claims{ - User: user, - Exp: expiryTimestamp, + claims := jwt.MapClaims{ + "user": user, + "exp": expiryTimestamp, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) diff --git a/internal/auth/types.go b/internal/auth/types.go index 8627df7..a83b941 100644 --- a/internal/auth/types.go +++ b/internal/auth/types.go @@ -1,10 +1,6 @@ package auth -import ( - "time" - - "github.com/golang-jwt/jwt/v5" -) +import "time" type TokenResponse struct { AccessToken string `json:"access_token"` @@ -32,9 +28,3 @@ 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 -} diff --git a/internal/types/types.go b/internal/types/types.go index 6120f08..c29a507 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -1,8 +1,6 @@ package types import ( - "time" - "github.com/gin-gonic/gin" "gorm.io/gorm" "stereo.cat/backend/internal/auth/client" @@ -18,14 +16,6 @@ type StereoConfig struct { ImagePath string Router *gin.Engine Client client.Client - 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"` - Base64 string `gorm:"type:text"` + Database *gorm.DB + JWTSecret string } diff --git a/main.go b/main.go index 54351ce..f8a4ae7 100644 --- a/main.go +++ b/main.go @@ -83,9 +83,9 @@ func main() { panic(errors.New("Invalid database type was specified.")) } - c.Database.AutoMigrate(&auth.User{}, &types.File{}) + c.Database.AutoMigrate(&auth.User{}) api.Register(&c) - fmt.Printf("Running on port %s\n", getEnv("PORT", "8080")) + c.Router.Run() }