diff --git a/.env.example b/.env.example
index b96334a..19c21ad 100644
--- a/.env.example
+++ b/.env.example
@@ -1,16 +1,16 @@
-IMAGE_PATH=/tmp
-REDIRECT_URI=http://localhost:8081/api/auth/callback
-CLIENT_ID=
-CLIENT_SECRET=
-
-# can be either postgres or sqlite
-DATABASE_TYPE=
-
-# database file, stereo.db by default.
-SQLITE_FILE=
-
-# postgres DSN, look at https://gorm.io/docs/connecting_to_the_database.html#PostgreSQL
-POSTGRES_DSN=
-
-# Random secret. Recommended length is 64 characters at minimum.
-JWT_SECRET=
+IMAGE_PATH=/tmp
+REDIRECT_URI=http://localhost:8081/api/auth/callback
+CLIENT_ID=
+CLIENT_SECRET=
+
+# can be either postgres or sqlite
+DATABASE_TYPE=
+
+# database file, stereo.db by default.
+SQLITE_FILE=
+
+# postgres DSN, look at https://gorm.io/docs/connecting_to_the_database.html#PostgreSQL
+POSTGRES_DSN=
+
+# Random secret. Recommended length is 64 characters at minimum.
+JWT_SECRET=
diff --git a/.gitignore b/.gitignore
index d17bf15..19a3a6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-.env
-tmp
-*.db
+.env
+tmp
+*.db
 imgs
\ No newline at end of file
diff --git a/README.md b/README.md
index b58e450..2c2667c 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# stereo.cat backend  
-  
-written in Go, uses Gin.
-
-## database shit  
-  
-Instead of using Discord oAuth as a database, we instead use it as a login source, only using it to source a username/id, avatar data and a secure login/registration flow.  
-We store these attributes alongside stereo.cat specific attributes in our own database. There is a trade-off however: this means that avatar & username data is not updated in real-time, only when the oauth flow is executed.
+# stereo.cat backend  
+  
+written in Go, uses Gin.
+
+## database shit  
+  
+Instead of using Discord oAuth as a database, we instead use it as a login source, only using it to source a username/id, avatar data and a secure login/registration flow.  
+We store these attributes alongside stereo.cat specific attributes in our own database. There is a trade-off however: this means that avatar & username data is not updated in real-time, only when the oauth flow is executed.
diff --git a/internal/api/api.go b/internal/api/api.go
index 592bc7b..bb61ef1 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -1,12 +1,12 @@
-package api
-
-import (
-	"stereo.cat/backend/internal/api/routes"
-	"stereo.cat/backend/internal/types"
-)
-
-func Register(cfg *types.StereoConfig) {
-	api := cfg.Router.Group("/api")
-	routes.RegisterFileRoutes(cfg, api)
-	routes.RegisterAuthRoutes(cfg, api)
-}
+package api
+
+import (
+	"stereo.cat/backend/internal/api/routes"
+	"stereo.cat/backend/internal/types"
+)
+
+func Register(cfg *types.StereoConfig) {
+	api := cfg.Router.Group("/api")
+	routes.RegisterFileRoutes(cfg, api)
+	routes.RegisterAuthRoutes(cfg, api)
+}
diff --git a/internal/api/routes/auth.go b/internal/api/routes/auth.go
index 53082dc..b4a2f38 100644
--- a/internal/api/routes/auth.go
+++ b/internal/api/routes/auth.go
@@ -1,51 +1,51 @@
-package routes
-
-import (
-	"net/http"
-	"time"
-
-	"github.com/gin-gonic/gin"
-	"stereo.cat/backend/internal/auth"
-	"stereo.cat/backend/internal/types"
-)
-
-func RegisterAuthRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) {
-	api.GET("/auth/callback", func(c *gin.Context) {
-		code := c.Query("code")
-
-		t, err := cfg.Client.ExchangeCode(code)
-
-		if err != nil {
-			panic(err)
-		}
-
-		user, err := cfg.Client.GetUser(t)
-
-		if err != nil {
-			panic(err)
-		}
-
-		jwt, err := auth.GenerateJWT(cfg.JWTSecret, user, uint64(time.Now().Add(time.Second*time.Duration(t.ExpiresIn)).Unix()))
-
-		if err != nil {
-			panic(err)
-		}
-
-		res := cfg.Database.FirstOrCreate(&user)
-
-		if res.Error != nil {
-			panic(res.Error)
-		}
-
-		// TODO: redirect to dashboard
-		c.JSON(http.StatusOK, gin.H{
-			"jwt":   jwt,
-			"known": res.RowsAffected == 0,
-		})
-	})
-
-	api.GET("/auth/me", auth.JwtMiddleware(cfg.JWTSecret), func(c *gin.Context) {
-		claims, _ := c.Get("claims")
-		c.JSON(http.StatusOK, claims)
-	})
-}
+package routes
+
+import (
+	"net/http"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"stereo.cat/backend/internal/auth"
+	"stereo.cat/backend/internal/types"
+)
+
+func RegisterAuthRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) {
+	api.GET("/auth/callback", func(c *gin.Context) {
+		code := c.Query("code")
+
+		t, err := cfg.Client.ExchangeCode(code)
+
+		if err != nil {
+			panic(err)
+		}
+
+		user, err := cfg.Client.GetUser(t)
+
+		if err != nil {
+			panic(err)
+		}
+
+		jwt, err := auth.GenerateJWT(cfg.JWTSecret, user, uint64(time.Now().Add(time.Second*time.Duration(t.ExpiresIn)).Unix()))
+
+		if err != nil {
+			panic(err)
+		}
+
+		res := cfg.Database.FirstOrCreate(&user)
+
+		if res.Error != nil {
+			panic(res.Error)
+		}
+
+		// TODO: redirect to dashboard
+		c.JSON(http.StatusOK, gin.H{
+			"jwt":   jwt,
+			"known": res.RowsAffected == 0,
+		})
+	})
+
+	api.GET("/auth/me", auth.JwtMiddleware(cfg.JWTSecret), func(c *gin.Context) {
+		claims, _ := c.Get("claims")
+		c.JSON(http.StatusOK, claims)
+	})
+}
diff --git a/internal/api/routes/files.go b/internal/api/routes/files.go
index 28accb4..c54489d 100644
--- a/internal/api/routes/files.go
+++ b/internal/api/routes/files.go
@@ -1,154 +1,144 @@
-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
-}
+package routes
+
+import (
+	"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
+		}
+
+		if file.Size <= 0 {
+			c.JSON(400, gin.H{"error": "file size must be greater than zero"})
+			return
+		}
+
+		fileMeta := types.File{
+			ID:        uid + "_" + file.Filename,
+			Path:      filePath,
+			Owner:     uid,
+			CreatedAt: time.Now(),
+			Size:      file.Size,
+		}
+
+		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)
+	})
+}
diff --git a/internal/auth/client/client.go b/internal/auth/client/client.go
index 3a922bc..661ee7a 100644
--- a/internal/auth/client/client.go
+++ b/internal/auth/client/client.go
@@ -1,117 +1,117 @@
-package client
-
-import (
-	"encoding/json"
-	"errors"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"strings"
-	"time"
-
-	"stereo.cat/backend/internal/auth"
-)
-
-type Client struct {
-	RedirectUri  string
-	ClientSecret string
-	ClientId     string
-}
-
-const api = "https://discord.com/api/v10"
-
-func New(redirectUri, clientId, clientSecret string) Client {
-	return Client{
-		RedirectUri:  redirectUri,
-		ClientId:     clientId,
-		ClientSecret: clientSecret,
-	}
-}
-
-func (c Client) GetUser(t auth.TokenResponse) (auth.User, error) {
-	user := auth.User{
-		Blacklisted: false,
-		CreatedAt:   time.Now(),
-	}
-
-	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", api, "users/@me"), nil)
-
-	if err != nil {
-		return user, nil
-	}
-
-	req.Header.Add("Content-Type", "application/json")
-	req.Header.Add("Authorization", "Bearer "+t.AccessToken)
-
-	resp, err := http.DefaultClient.Do(req)
-
-	if resp.StatusCode != http.StatusOK {
-		bodyBytes, err := io.ReadAll(resp.Body)
-
-		if err != nil {
-			return user, err
-		}
-
-		return user, errors.New(string(bodyBytes))
-	}
-
-	if err != nil {
-		return user, err
-	}
-
-	defer resp.Body.Close()
-
-	err = json.NewDecoder(resp.Body).Decode(&user)
-
-	if err != nil {
-		return user, err
-	}
-
-	return user, nil
-}
-
-func (c Client) ExchangeCode(code string) (auth.TokenResponse, error) {
-	var tokenResponse auth.TokenResponse
-
-	requestBody := url.Values{
-		"grant_type":   {"authorization_code"},
-		"code":         {code},
-		"redirect_uri": {c.RedirectUri},
-	}
-
-	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s", api, "/oauth2/token"), strings.NewReader(requestBody.Encode()))
-
-	if err != nil {
-		return tokenResponse, err
-	}
-
-	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-	req.SetBasicAuth(c.ClientId, c.ClientSecret)
-
-	resp, err := http.DefaultClient.Do(req)
-
-	if resp.StatusCode != http.StatusOK {
-		bodyBytes, err := io.ReadAll(resp.Body)
-
-		if err != nil {
-			return tokenResponse, err
-		}
-
-		return tokenResponse, errors.New(string(bodyBytes))
-	}
-
-	if err != nil {
-		return tokenResponse, err
-	}
-
-	defer resp.Body.Close()
-
-	err = json.NewDecoder(resp.Body).Decode(&tokenResponse)
-
-	if err != nil {
-		return tokenResponse, err
-	}
-
-	return tokenResponse, nil
-}
+package client
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	"stereo.cat/backend/internal/auth"
+)
+
+type Client struct {
+	RedirectUri  string
+	ClientSecret string
+	ClientId     string
+}
+
+const api = "https://discord.com/api/v10"
+
+func New(redirectUri, clientId, clientSecret string) Client {
+	return Client{
+		RedirectUri:  redirectUri,
+		ClientId:     clientId,
+		ClientSecret: clientSecret,
+	}
+}
+
+func (c Client) GetUser(t auth.TokenResponse) (auth.User, error) {
+	user := auth.User{
+		Blacklisted: false,
+		CreatedAt:   time.Now(),
+	}
+
+	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", api, "users/@me"), nil)
+
+	if err != nil {
+		return user, nil
+	}
+
+	req.Header.Add("Content-Type", "application/json")
+	req.Header.Add("Authorization", "Bearer "+t.AccessToken)
+
+	resp, err := http.DefaultClient.Do(req)
+
+	if resp.StatusCode != http.StatusOK {
+		bodyBytes, err := io.ReadAll(resp.Body)
+
+		if err != nil {
+			return user, err
+		}
+
+		return user, errors.New(string(bodyBytes))
+	}
+
+	if err != nil {
+		return user, err
+	}
+
+	defer resp.Body.Close()
+
+	err = json.NewDecoder(resp.Body).Decode(&user)
+
+	if err != nil {
+		return user, err
+	}
+
+	return user, nil
+}
+
+func (c Client) ExchangeCode(code string) (auth.TokenResponse, error) {
+	var tokenResponse auth.TokenResponse
+
+	requestBody := url.Values{
+		"grant_type":   {"authorization_code"},
+		"code":         {code},
+		"redirect_uri": {c.RedirectUri},
+	}
+
+	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s", api, "/oauth2/token"), strings.NewReader(requestBody.Encode()))
+
+	if err != nil {
+		return tokenResponse, err
+	}
+
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+	req.SetBasicAuth(c.ClientId, c.ClientSecret)
+
+	resp, err := http.DefaultClient.Do(req)
+
+	if resp.StatusCode != http.StatusOK {
+		bodyBytes, err := io.ReadAll(resp.Body)
+
+		if err != nil {
+			return tokenResponse, err
+		}
+
+		return tokenResponse, errors.New(string(bodyBytes))
+	}
+
+	if err != nil {
+		return tokenResponse, err
+	}
+
+	defer resp.Body.Close()
+
+	err = json.NewDecoder(resp.Body).Decode(&tokenResponse)
+
+	if err != nil {
+		return tokenResponse, err
+	}
+
+	return tokenResponse, nil
+}
diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go
index 1ea6d0f..46cea85 100644
--- a/internal/auth/jwt.go
+++ b/internal/auth/jwt.go
@@ -1,83 +1,83 @@
-package auth
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"strings"
-
-	"github.com/gin-gonic/gin"
-	"github.com/golang-jwt/jwt/v5"
-)
-
-func GenerateJWT(key string, user User, expiryTimestamp uint64) (string, error) {
-	claims := Claims{
-		User: user,
-		Exp:  expiryTimestamp,
-	}
-
-	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-	return token.SignedString([]byte(key))
-}
-
-func invalidAuth(c *gin.Context) {
-	c.String(http.StatusUnauthorized, "Unauthorized.")
-	c.Abort()
-}
-
-func JwtMiddleware(secret string) gin.HandlerFunc {
-	return func(c *gin.Context) {
-		jwtSplit := strings.Split(c.GetHeader("Authorization"), " ")
-
-		if len(jwtSplit) < 2 || jwtSplit[0] != "Bearer" {
-			invalidAuth(c)
-			return
-		}
-
-		claims, err := ValidateJWT(jwtSplit[1], secret)
-		if err != nil {
-			invalidAuth(c)
-			return
-		}
-
-		if userClaims, ok := claims["user"].(map[string]interface{}); ok {
-			userJSON, err := json.Marshal(userClaims) // Convert map to JSON
-			if err != nil {
-				invalidAuth(c)
-				return
-			}
-
-			var user User
-			err = json.Unmarshal(userJSON, &user)
-			if err != nil {
-				invalidAuth(c)
-				return
-			}
-
-			claims["user"] = user
-		}
-
-		c.Set("claims", claims)
-		c.Next()
-	}
-}
-
-func ValidateJWT(jwtString, key string) (jwt.MapClaims, error) {
-	token, err := jwt.Parse(jwtString, func(token *jwt.Token) (any, error) {
-		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
-			return nil, fmt.Errorf("Invalid signing method!")
-		}
-
-		return []byte(key), nil
-	})
-
-	if err != nil {
-		return nil, err
-	}
-
-	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
-		return claims, nil
-	}
-
-	return nil, fmt.Errorf("Invalid token!")
-}
+package auth
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+	"github.com/golang-jwt/jwt/v5"
+)
+
+func GenerateJWT(key string, user User, expiryTimestamp uint64) (string, error) {
+	claims := Claims{
+		User: user,
+		Exp:  expiryTimestamp,
+	}
+
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	return token.SignedString([]byte(key))
+}
+
+func invalidAuth(c *gin.Context) {
+	c.String(http.StatusUnauthorized, "Unauthorized.")
+	c.Abort()
+}
+
+func JwtMiddleware(secret string) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		jwtSplit := strings.Split(c.GetHeader("Authorization"), " ")
+
+		if len(jwtSplit) < 2 || jwtSplit[0] != "Bearer" {
+			invalidAuth(c)
+			return
+		}
+
+		claims, err := ValidateJWT(jwtSplit[1], secret)
+		if err != nil {
+			invalidAuth(c)
+			return
+		}
+
+		if userClaims, ok := claims["user"].(map[string]interface{}); ok {
+			userJSON, err := json.Marshal(userClaims) // Convert map to JSON
+			if err != nil {
+				invalidAuth(c)
+				return
+			}
+
+			var user User
+			err = json.Unmarshal(userJSON, &user)
+			if err != nil {
+				invalidAuth(c)
+				return
+			}
+
+			claims["user"] = user
+		}
+
+		c.Set("claims", claims)
+		c.Next()
+	}
+}
+
+func ValidateJWT(jwtString, key string) (jwt.MapClaims, error) {
+	token, err := jwt.Parse(jwtString, func(token *jwt.Token) (any, error) {
+		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, fmt.Errorf("Invalid signing method!")
+		}
+
+		return []byte(key), nil
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
+		return claims, nil
+	}
+
+	return nil, fmt.Errorf("Invalid token!")
+}
diff --git a/internal/auth/types.go b/internal/auth/types.go
index 8627df7..361cebe 100644
--- a/internal/auth/types.go
+++ b/internal/auth/types.go
@@ -1,40 +1,40 @@
-package auth
-
-import (
-	"time"
-
-	"github.com/golang-jwt/jwt/v5"
-)
-
-type TokenResponse struct {
-	AccessToken  string `json:"access_token"`
-	TokenType    string `json:"token_type"`
-	ExpiresIn    int64  `json:"expires_in"`
-	RefreshToken string `json:"refresh_token"`
-	Scope        string `json:"scope"`
-}
-
-type User struct {
-	ID          string    `json:"id" gorm:"primaryKey"`
-	Username    string    `json:"username"`
-	Blacklisted bool      `json:"blacklisted"`
-	Email       string    `json:"email"`
-	CreatedAt   time.Time `json:"created_at"`
-}
-
-type AvatarDecorationData struct {
-	Asset string
-	SkuID string
-}
-
-type ExchangeCodeRequest struct {
-	GrantType   string `json:"grant_type"`
-	Code        string `json:"code"`
-	RedirectUri string `json:"redirect_uri"`
-}
-
-type Claims struct {
-	User User   `json:"user"`
-	Exp  uint64 `json:"exp"`
-	jwt.RegisteredClaims
-}
+package auth
+
+import (
+	"time"
+
+	"github.com/golang-jwt/jwt/v5"
+)
+
+type TokenResponse struct {
+	AccessToken  string `json:"access_token"`
+	TokenType    string `json:"token_type"`
+	ExpiresIn    int64  `json:"expires_in"`
+	RefreshToken string `json:"refresh_token"`
+	Scope        string `json:"scope"`
+}
+
+type User struct {
+	ID          string    `json:"id" gorm:"primaryKey"`
+	Username    string    `json:"username"`
+	Blacklisted bool      `json:"blacklisted"`
+	Email       string    `json:"email"`
+	CreatedAt   time.Time `json:"created_at"`
+}
+
+type AvatarDecorationData struct {
+	Asset string
+	SkuID string
+}
+
+type ExchangeCodeRequest struct {
+	GrantType   string `json:"grant_type"`
+	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..505c1fe 100644
--- a/internal/types/types.go
+++ b/internal/types/types.go
@@ -1,31 +1,31 @@
-package types
-
-import (
-	"time"
-
-	"github.com/gin-gonic/gin"
-	"gorm.io/gorm"
-	"stereo.cat/backend/internal/auth/client"
-)
-
-type Route struct {
-	Path   string
-	Method string
-	Exec   func(cfg *StereoConfig) gin.HandlerFunc
-}
-
-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"`
-}
+package types
+
+import (
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"gorm.io/gorm"
+	"stereo.cat/backend/internal/auth/client"
+)
+
+type Route struct {
+	Path   string
+	Method string
+	Exec   func(cfg *StereoConfig) gin.HandlerFunc
+}
+
+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"`
+	Size      int64     `gorm:"not null;type:bigint"`
+	CreatedAt time.Time `gorm:"autoCreateTime"`
+}
diff --git a/main.go b/main.go
index 54351ce..92466c0 100644
--- a/main.go
+++ b/main.go
@@ -1,91 +1,91 @@
-package main
-
-import (
-	"errors"
-	"fmt"
-	"log"
-	"os"
-
-	"github.com/gin-gonic/gin"
-	"github.com/joho/godotenv"
-	"gorm.io/driver/postgres"
-	"gorm.io/driver/sqlite"
-	"gorm.io/gorm"
-	"stereo.cat/backend/internal/api"
-	"stereo.cat/backend/internal/auth"
-	"stereo.cat/backend/internal/auth/client"
-	"stereo.cat/backend/internal/types"
-)
-
-func getEnv(key, fallback string) string {
-	if value, ok := os.LookupEnv(key); ok {
-		return value
-	}
-	return fallback
-}
-
-func requireEnv(key string) string {
-	if value, ok := os.LookupEnv(key); ok {
-		return value
-	}
-	panic(errors.New(fmt.Sprintf("Environment variable %s is required but not specified. Exiting...", key)))
-}
-
-func main() {
-	_ = godotenv.Load()
-
-	databaseType := getEnv("DATABASE_TYPE", "sqlite")
-	sqliteFile := getEnv("SQLITE_FILE", "stereo.db")
-	imagePath := getEnv("IMAGE_PATH", os.TempDir())
-
-	if _, err := os.Stat(imagePath); err != nil {
-		if os.IsNotExist(err) {
-			if err := os.MkdirAll(imagePath, os.ModePerm); err != nil {
-				log.Fatal(err)
-			}
-		}
-	}
-
-	c := types.StereoConfig{
-		Router:    gin.Default(),
-		ImagePath: imagePath,
-		Client: client.New(
-			requireEnv("REDIRECT_URI"),
-			requireEnv("CLIENT_ID"),
-			requireEnv("CLIENT_SECRET"),
-		),
-		JWTSecret: requireEnv("JWT_SECRET"),
-	}
-
-	switch databaseType {
-	case "sqlite":
-		db, err := gorm.Open(sqlite.Open(sqliteFile), &gorm.Config{})
-
-		c.Database = db
-
-		if err != nil {
-			panic(err)
-		}
-
-		break
-
-	case "postgres":
-		db, err := gorm.Open(postgres.Open(requireEnv("POSTGRES_DSN")), &gorm.Config{})
-
-		c.Database = db
-
-		if err != nil {
-			panic(err)
-		}
-
-		break
-	default:
-		panic(errors.New("Invalid database type was specified."))
-	}
-
-	c.Database.AutoMigrate(&auth.User{}, &types.File{})
-
-	api.Register(&c)
-	fmt.Printf("Running on port %s\n", getEnv("PORT", "8080"))
-	c.Router.Run()
-}
+package main
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"os"
+
+	"github.com/gin-gonic/gin"
+	"github.com/joho/godotenv"
+	"gorm.io/driver/postgres"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	"stereo.cat/backend/internal/api"
+	"stereo.cat/backend/internal/auth"
+	"stereo.cat/backend/internal/auth/client"
+	"stereo.cat/backend/internal/types"
+)
+
+func getEnv(key, fallback string) string {
+	if value, ok := os.LookupEnv(key); ok {
+		return value
+	}
+	return fallback
+}
+
+func requireEnv(key string) string {
+	if value, ok := os.LookupEnv(key); ok {
+		return value
+	}
+	panic(errors.New(fmt.Sprintf("Environment variable %s is required but not specified. Exiting...", key)))
+}
+
+func main() {
+	_ = godotenv.Load()
+
+	databaseType := getEnv("DATABASE_TYPE", "sqlite")
+	sqliteFile := getEnv("SQLITE_FILE", "stereo.db")
+	imagePath := getEnv("IMAGE_PATH", os.TempDir())
+
+	if _, err := os.Stat(imagePath); err != nil {
+		if os.IsNotExist(err) {
+			if err := os.MkdirAll(imagePath, os.ModePerm); err != nil {
+				log.Fatal(err)
+			}
+		}
+	}
+
+	c := types.StereoConfig{
+		Router:    gin.Default(),
+		ImagePath: imagePath,
+		Client: client.New(
+			requireEnv("REDIRECT_URI"),
+			requireEnv("CLIENT_ID"),
+			requireEnv("CLIENT_SECRET"),
+		),
+		JWTSecret: requireEnv("JWT_SECRET"),
+	}
+
+	switch databaseType {
+	case "sqlite":
+		db, err := gorm.Open(sqlite.Open(sqliteFile), &gorm.Config{})
+
+		c.Database = db
+
+		if err != nil {
+			panic(err)
+		}
+
+		break
+
+	case "postgres":
+		db, err := gorm.Open(postgres.Open(requireEnv("POSTGRES_DSN")), &gorm.Config{})
+
+		c.Database = db
+
+		if err != nil {
+			panic(err)
+		}
+
+		break
+	default:
+		panic(errors.New("Invalid database type was specified."))
+	}
+
+	c.Database.AutoMigrate(&auth.User{}, &types.File{})
+
+	api.Register(&c)
+	fmt.Printf("Running on port %s\n", getEnv("PORT", "8080"))
+	c.Router.Run()
+}