package handlers import ( "bookmann/internal/database" "fmt" "net/http" "path/filepath" "strings" "time" jwt "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" "golang.org/x/crypto/bcrypt" ) type AuthHandler struct { db *database.Queries jwtKey []byte } func NewAuthHandler(db *database.Queries, jwtSecret string) *AuthHandler { return &AuthHandler{ db: db, jwtKey: []byte(jwtSecret), } } type RegisterRequest struct { Email string `form:"email" json:"email" validate:"required,email"` Username string `form:"username" json:"username" validate:"required,min=3,max=50"` Password string `form:"password" json:"password" validate:"required,min=6"` FirstName string `form:"first_name" json:"first_name,omitempty"` LastName string `form:"last_name" json:"last_name,omitempty"` } type LoginRequest struct { Login string `form:"login" json:"login" validate:"required"` // email or username Password string `form:"password" json:"password" validate:"required"` } type AuthResponse struct { Token string `json:"token"` User UserProfile `json:"user"` } type UserProfile struct { ID string `json:"id"` Email string `json:"email"` Username string `json:"username"` FirstName string `json:"first_name"` LastName string `json:"last_name"` } type UpdateProfileRequest struct { FirstName string `json:"first_name,omitempty"` LastName string `json:"last_name,omitempty"` } // Register handles POST /api/auth/register func (h *AuthHandler) Register(c echo.Context) error { // Try form data first (HTMX), then JSON (Bruno) email := c.FormValue("email") username := c.FormValue("username") password := c.FormValue("password") firstName := c.FormValue("first_name") lastName := c.FormValue("last_name") if email == "" || username == "" || password == "" { // Fallback to JSON binding req := RegisterRequest{} if err := c.Bind(&req); err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusBadRequest, `
Invalid request
`) } return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusBadRequest, `
`+err.Error()+`
`) } return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } email = req.Email username = req.Username password = req.Password firstName = req.FirstName lastName = req.LastName } req := RegisterRequest{Email: email, Username: username, Password: password, FirstName: firstName, LastName: lastName} if err := c.Validate(&req); err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusBadRequest, `
`+err.Error()+`
`) } return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } // Check if user already exists if _, err := h.db.GetUserByEmail(c.Request().Context(), req.Email); err == nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusConflict, `
Email already exists
`) } return c.JSON(http.StatusConflict, map[string]string{"error": "email already exists"}) } if _, err := h.db.GetUserByUsername(c.Request().Context(), req.Username); err == nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusConflict, `
Username already exists
`) } return c.JSON(http.StatusConflict, map[string]string{"error": "username already exists"}) } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusInternalServerError, `
Failed to hash password
`) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to hash password"}) } // Create user user, err := h.db.CreateUser(c.Request().Context(), database.CreateUserParams{ Email: req.Email, Username: req.Username, PasswordHash: string(hashedPassword), FirstName: pgtype.Text{String: req.FirstName, Valid: req.FirstName != ""}, LastName: pgtype.Text{String: req.LastName, Valid: req.LastName != ""}, }) if err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusInternalServerError, `
`+err.Error()+`
`) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } // Generate JWT token, err := h.generateJWT(uuid.UUID(user.ID.Bytes).String()) if err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusInternalServerError, `
Failed to generate token
`) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate token"}) } // Check if request is from HTMX if c.Request().Header.Get("HX-Request") == "true" { // Return HTML with script to set token and redirect html := fmt.Sprintf(`
Registration successful! Redirecting...
`, token, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username), token) return c.HTML(http.StatusCreated, html) } return c.JSON(http.StatusCreated, AuthResponse{ Token: token, User: UserProfile{ ID: uuid.UUID(user.ID.Bytes).String(), Email: user.Email, Username: user.Username, }, }) } // Login handles POST /api/auth/login func (h *AuthHandler) Login(c echo.Context) error { // Debug logging fmt.Printf("Login request - Content-Type: %s\n", c.Request().Header.Get("Content-Type")) fmt.Printf("Form values - login: %s, password: %s\n", c.FormValue("login"), c.FormValue("password")) // Try form data first (HTMX), then JSON (Bruno) login := c.FormValue("login") password := c.FormValue("password") if login == "" || password == "" { fmt.Printf("Form values empty, trying JSON bind\n") // Fallback to JSON binding req := LoginRequest{} if err := c.Bind(&req); err != nil { fmt.Printf("JSON bind error: %v\n", err) if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusBadRequest, `
Invalid request
`) } return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { fmt.Printf("Validation error: %v\n", err) if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusBadRequest, `
`+err.Error()+`
`) } return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } login = req.Login password = req.Password fmt.Printf("JSON bind success - login: %s\n", login) } req := LoginRequest{Login: login, Password: password} if err := c.Validate(&req); err != nil { fmt.Printf("Final validation error: %v\n", err) if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusBadRequest, `
`+err.Error()+`
`) } return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } // Get user by email or username (includes password hash for verification) user, err := h.db.GetUserForLogin(c.Request().Context(), req.Login) if err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusUnauthorized, `
Invalid credentials
`) } return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) } // Check password if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusUnauthorized, `
Invalid credentials
`) } return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) } // Generate JWT token, err := h.generateJWT(uuid.UUID(user.ID.Bytes).String()) if err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusInternalServerError, `
Failed to generate token
`) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate token"}) } // Check if request is from HTMX if c.Request().Header.Get("HX-Request") == "true" { // Return HTML with script to set token and redirect html := fmt.Sprintf(`
Login successful! Redirecting...
`, token, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s","first_name":"%s","last_name":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username, user.FirstName.String, user.LastName.String), token) return c.HTML(http.StatusOK, html) } firstName := "" if user.FirstName.Valid { firstName = user.FirstName.String } lastName := "" if user.LastName.Valid { lastName = user.LastName.String } return c.JSON(http.StatusOK, AuthResponse{ Token: token, User: UserProfile{ ID: uuid.UUID(user.ID.Bytes).String(), Email: user.Email, Username: user.Username, FirstName: firstName, LastName: lastName, }, }) } // GetProfile handles GET /api/auth/profile func (h *AuthHandler) GetProfile(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } user, err := h.db.GetUser(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true}) if err != nil { return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"}) } firstName := "" if user.FirstName.Valid { firstName = user.FirstName.String } lastName := "" if user.LastName.Valid { lastName = user.LastName.String } return c.JSON(http.StatusOK, UserProfile{ ID: uuid.UUID(user.ID.Bytes).String(), Email: user.Email, Username: user.Username, FirstName: firstName, LastName: lastName, }) } // UpdateProfile handles PUT /api/auth/profile func (h *AuthHandler) UpdateProfile(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req UpdateProfileRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } err = h.db.UpdateUserProfile(c.Request().Context(), database.UpdateUserProfileParams{ ID: pgtype.UUID{Bytes: userUUID, Valid: true}, FirstName: pgtype.Text{String: req.FirstName, Valid: req.FirstName != ""}, LastName: pgtype.Text{String: req.LastName, Valid: req.LastName != ""}, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"message": "profile updated"}) } // ListUsers handles GET /api/users func (h *AuthHandler) ListUsers(c echo.Context) error { users, err := h.db.ListUsers(c.Request().Context()) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } type UserList struct { ID string `json:"id"` Email string `json:"email"` Username string `json:"username"` Theme string `json:"theme"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } var userList []UserList for _, u := range users { theme := "" if u.Theme.Valid { theme = u.Theme.String } createdAt := "" if u.CreatedAt.Valid { createdAt = u.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00") } updatedAt := "" if u.UpdatedAt.Valid { updatedAt = u.UpdatedAt.Time.Format("2006-01-02T15:04:05Z07:00") } userList = append(userList, UserList{ ID: uuid.UUID(u.ID.Bytes).String(), Email: u.Email, Username: u.Username, Theme: theme, CreatedAt: createdAt, UpdatedAt: updatedAt, }) } return c.JSON(http.StatusOK, userList) } type AddEbookFolderRequest struct { FolderPath string `json:"folder_path" validate:"required"` } // normalizePath cleans and normalizes folder paths for consistent storage and comparison func normalizePath(path string) string { fmt.Printf("normalizePath input: '%s'\n", path) var cleaned string // Handle home directory expansion (~) if strings.HasPrefix(path, "~/") { // Keep the original path for ~ to preserve user's formatting // Just normalize separators and that's it cleaned = strings.ReplaceAll(path, "\\", "/") } else { // Clean the path to remove redundant separators, ., .. etc. cleaned = filepath.Clean(path) // Convert to consistent path separators (use forward slashes for storage) cleaned = strings.ReplaceAll(cleaned, "\\", "/") // Remove trailing slash unless it's root path if len(cleaned) > 1 && strings.HasSuffix(cleaned, "/") { cleaned = strings.TrimSuffix(cleaned, "/") } } fmt.Printf("normalizePath output: '%s'\n", cleaned) return cleaned } type EbookFolderResponse struct { ID string `json:"id"` UserID string `json:"user_id"` FolderPath string `json:"folder_path"` CreatedAt string `json:"created_at"` } type DeleteEbookFolderRequest struct { FolderPath string `json:"folder_path" validate:"required"` } // AddEbookFolder handles POST /api/auth/ebook-folders func (h *AuthHandler) AddEbookFolder(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req AddEbookFolderRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } // Normalize the folder path before storing normalizedPath := normalizePath(req.FolderPath) folder, err := h.db.AddUserEbookFolder(c.Request().Context(), database.AddUserEbookFolderParams{ UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, FolderPath: normalizedPath, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusCreated, EbookFolderResponse{ ID: uuid.UUID(folder.ID.Bytes).String(), UserID: uuid.UUID(folder.UserID.Bytes).String(), FolderPath: folder.FolderPath, CreatedAt: folder.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00"), }) } // GetEbookFolders handles GET /api/auth/ebook-folders func (h *AuthHandler) GetEbookFolders(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } folders, err := h.db.GetUserEbookFolders(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true}) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } fmt.Printf("GetEbookFolders: user_id='%s'\n", userID) for _, folder := range folders { fmt.Printf(" Folder in DB: id='%s', path='%s'\n", uuid.UUID(folder.ID.Bytes).String(), folder.FolderPath) } var response []EbookFolderResponse for _, folder := range folders { response = append(response, EbookFolderResponse{ ID: uuid.UUID(folder.ID.Bytes).String(), UserID: uuid.UUID(folder.UserID.Bytes).String(), FolderPath: folder.FolderPath, CreatedAt: folder.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00"), }) } return c.JSON(http.StatusOK, response) } // DeleteEbookFolder handles DELETE /api/auth/ebook-folders func (h *AuthHandler) DeleteEbookFolder(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req DeleteEbookFolderRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } // Normalize the folder path before deletion normalizedPath := normalizePath(req.FolderPath) // Debug logging - remove in production fmt.Printf("DeleteEbookFolder: original path='%s', normalized path='%s', user_id='%s'\n", req.FolderPath, normalizedPath, userID) deletedFolder, err := h.db.DeleteUserEbookFolder(c.Request().Context(), database.DeleteUserEbookFolderParams{ UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, FolderPath: normalizedPath, }) if err != nil { fmt.Printf("DeleteEbookFolder failed: %v\n", err) if err == pgx.ErrNoRows { return c.JSON(http.StatusNotFound, map[string]string{"error": "ebook folder not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } fmt.Printf("DeleteEbookFolder succeeded: deleted folder with path '%s'\n", deletedFolder.FolderPath) return c.JSON(http.StatusOK, map[string]string{"message": "ebook folder deleted successfully"}) } type UpdateThemeRequest struct { Theme string `json:"theme" validate:"required"` } // UpdateTheme handles PUT /api/auth/theme func (h *AuthHandler) UpdateTheme(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req UpdateThemeRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } err = h.db.UpdateUserTheme(c.Request().Context(), database.UpdateUserThemeParams{ ID: pgtype.UUID{Bytes: userUUID, Valid: true}, Theme: pgtype.Text{String: req.Theme, Valid: req.Theme != ""}, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"message": "theme updated successfully"}) } type UpdateUsernameRequest struct { Username string `json:"username" validate:"required,min=3,max=50"` } // UpdateUsername handles PUT /api/user/username func (h *AuthHandler) UpdateUsername(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req UpdateUsernameRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } // Check if username is already taken by another user existingUser, err := h.db.GetUserByUsername(c.Request().Context(), req.Username) if err == nil && uuid.UUID(existingUser.ID.Bytes) != userUUID { return c.JSON(http.StatusConflict, map[string]string{"error": "username already taken"}) } // Update username err = h.db.UpdateUsername(c.Request().Context(), database.UpdateUsernameParams{ ID: pgtype.UUID{Bytes: userUUID, Valid: true}, Username: req.Username, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"message": "username updated successfully"}) } type UpdateEmailRequest struct { Email string `json:"email" validate:"required,email"` } // UpdateEmail handles PUT /api/user/email func (h *AuthHandler) UpdateEmail(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req UpdateEmailRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } // Check if email is already taken by another user existingUser, err := h.db.GetUserByEmail(c.Request().Context(), req.Email) if err == nil && uuid.UUID(existingUser.ID.Bytes) != userUUID { return c.JSON(http.StatusConflict, map[string]string{"error": "email already taken"}) } // Update email err = h.db.UpdateEmail(c.Request().Context(), database.UpdateEmailParams{ ID: pgtype.UUID{Bytes: userUUID, Valid: true}, Email: req.Email, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"message": "email updated successfully"}) } type UpdatePasswordRequest struct { CurrentPassword string `json:"current_password" validate:"required"` NewPassword string `json:"new_password" validate:"required,min=6"` ConfirmPassword string `json:"confirm_password" validate:"required"` } // UpdatePassword handles PUT /api/user/password func (h *AuthHandler) UpdatePassword(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req UpdatePasswordRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } // Check if new passwords match if req.NewPassword != req.ConfirmPassword { return c.JSON(http.StatusBadRequest, map[string]string{"error": "new passwords do not match"}) } // Get current user's password hash passwordHash, err := h.db.GetUserPasswordHash(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true}) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get user"}) } // Verify current password if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.CurrentPassword)); err != nil { return c.JSON(http.StatusUnauthorized, map[string]string{"error": "current password is incorrect"}) } // Hash new password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to hash password"}) } // Update password err = h.db.UpdatePassword(c.Request().Context(), database.UpdatePasswordParams{ ID: pgtype.UUID{Bytes: userUUID, Valid: true}, PasswordHash: string(hashedPassword), }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"message": "password updated successfully"}) } // DeleteAccount handles DELETE /api/user/account func (h *AuthHandler) DeleteAccount(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } // Delete user (this will cascade to delete all related data) err = h.db.DeleteUser(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true}) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"message": "account deleted successfully"}) } type UpdateScanSettingsRequest struct { ScanFrequencyMinutes int32 `json:"scan_frequency_minutes" validate:"required,min=15,max=1440"` AutoScanEnabled bool `json:"auto_scan_enabled"` } // UpdateScanSettings handles PUT /api/library/scan-settings func (h *AuthHandler) UpdateScanSettings(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } var req UpdateScanSettingsRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } if err := c.Validate(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } err = h.db.UpdateScanSettings(c.Request().Context(), database.UpdateScanSettingsParams{ ID: pgtype.UUID{Bytes: userUUID, Valid: true}, ScanFrequencyMinutes: pgtype.Int4{Int32: req.ScanFrequencyMinutes, Valid: true}, AutoScanEnabled: pgtype.Bool{Bool: req.AutoScanEnabled, Valid: true}, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"message": "scan settings updated successfully"}) } // GetScanSettings handles GET /api/library/scan-settings func (h *AuthHandler) GetScanSettings(c echo.Context) error { userID := c.Get("user_id").(string) userUUID, err := uuid.Parse(userID) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) } settings, err := h.db.GetScanSettings(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true}) if err != nil { // If no settings found, return defaults return c.JSON(http.StatusOK, map[string]interface{}{ "scan_frequency_minutes": 60, "auto_scan_enabled": true, }) } return c.JSON(http.StatusOK, map[string]interface{}{ "scan_frequency_minutes": settings.ScanFrequencyMinutes.Int32, "auto_scan_enabled": settings.AutoScanEnabled.Bool, }) } func (h *AuthHandler) generateJWT(userID string) (string, error) { claims := jwt.MapClaims{ "user_id": userID, "exp": time.Now().Add(24 * time.Hour).Unix(), "iat": time.Now().Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(h.jwtKey) }