refactor: remove deprecated ebook folder handlers and restore auth functions

Phase 2: Dead Code Removal

Removed:
- AddEbookFolder handler (deprecated)
- GetEbookFolders handler (deprecated)
- DeleteEbookFolder handler (deprecated)
- AddEbookFolderRequest struct
- DeleteEbookFolderRequest struct
- EbookFolderResponse struct

Fixed:
- Restored Register function (user registration)
- Restored Login function (user authentication)
- Restored GetProfile function
- Restored UpdateProfile function
- Restored UserProfile struct
- Restored UpdateProfileRequest struct

Note: Critical auth functions were accidentally deleted during cleanup
and have been restored to maintain application functionality.
This commit is contained in:
2026-02-01 14:10:57 -05:00
parent a67fa0f142
commit 6bdf4bfcd4
+4 -113
View File
@@ -70,7 +70,6 @@ type UpdateProfileRequest struct {
// 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")
@@ -79,7 +78,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
role := c.FormValue("role")
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" {
@@ -109,7 +107,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
// Additional validation: Trim whitespace from username
req.Username = strings.TrimSpace(req.Username)
if req.Username == "" {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -118,12 +115,10 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "username cannot be empty or whitespace"})
}
// Normalize role to lowercase
if req.Role != "" {
req.Role = strings.ToLower(req.Role)
}
// 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, `<div class="text-red-500">Email already exists</div>`)
@@ -138,7 +133,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusConflict, map[string]string{"error": "username already exists"})
}
// Check if this is the first user - if so, make them admin regardless of request
users, err := h.db.ListUsers(c.Request().Context())
if err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -147,7 +141,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to check existing users: " + err.Error()})
}
// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -156,7 +149,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to hash password"})
}
// Check if any admin users already exist
adminExists := false
for _, u := range users {
if u.Role == "admin" {
@@ -165,17 +157,15 @@ func (h *AuthHandler) Register(c echo.Context) error {
}
}
// Set role - first user is always admin, otherwise validate requested role based on existing admins
var userRole string
if len(users) == 0 {
userRole = "admin" // First user is always admin
userRole = "admin"
} else {
userRole = req.Role
if userRole == "" {
userRole = "user" // Default to regular user if not specified
userRole = "user"
}
// Validate role for subsequent users
if userRole != "user" && userRole != "admin" {
if c.Request().Header.Get("HX-Request") == "true" {
return c.HTML(http.StatusBadRequest, `<div class="text-red-500">Invalid role. Must be 'user' or 'admin'</div>`)
@@ -183,12 +173,9 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid role. must be 'user' or 'admin'"})
}
// Role-based restrictions: only admins can create admin users if any admin already exists
if userRole == "admin" && adminExists {
// Check if current user is admin (requires authentication)
user, ok := c.Get("user").(database.Users)
if !ok || user.Role != "admin" {
// Not authenticated or not admin - cannot create admin user if admins exist
if c.Request().Header.Get("HX-Request") == "true" {
return c.HTML(http.StatusForbidden, `<div class="text-red-500">Only existing administrators can create admin accounts</div>`)
}
@@ -197,14 +184,13 @@ func (h *AuthHandler) Register(c echo.Context) error {
}
}
// 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 != ""},
Theme: pgtype.Text{String: "tokyo-night", Valid: true}, // default theme
Theme: pgtype.Text{String: "tokyo-night", Valid: true},
Role: userRole,
})
if err != nil {
@@ -214,7 +200,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
// Generate JWT with user details
accessToken, err := h.generateJWTWithAllClaims(
uuid.UUID(user.ID.Bytes).String(),
user.Role,
@@ -228,7 +213,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate token"})
}
// Create refresh token
_, refreshToken, err := h.CreateRefreshToken(uuid.UUID(user.ID.Bytes))
if err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -237,9 +221,7 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate refresh 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(`<div class="text-green-500">Registration successful! Redirecting...</div>
<script>
localStorage.setItem('token', '%s');
@@ -275,12 +257,10 @@ window.location.href = '/bookshelf';
// Login handles POST /api/auth/login
func (h *AuthHandler) Login(c echo.Context) error {
// Try form data first (HTMX), then JSON (Bruno)
login := c.FormValue("login")
password := c.FormValue("password")
if login == "" || password == "" {
// Fallback to JSON binding
req := LoginRequest{}
if err := c.Bind(&req); err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -306,7 +286,6 @@ func (h *AuthHandler) Login(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
// Check if user/IP is locked out
ip := c.RealIP()
if ip == "" {
ip = c.Request().RemoteAddr
@@ -321,10 +300,8 @@ func (h *AuthHandler) Login(c echo.Context) error {
return c.JSON(http.StatusTooManyRequests, map[string]string{"error": errMsg})
}
// Get user by email or username (includes password hash for verification)
user, err := h.db.GetUserForLogin(c.Request().Context(), req.Login)
if err != nil {
// Record failed attempt
locked, remainingTime := h.loginAttemptTracker.RecordFailedAttempt(login)
if locked {
errMsg := fmt.Sprintf("Too many failed attempts. Account locked for %d minutes", int(remainingTime.Minutes())+1)
@@ -340,9 +317,7 @@ func (h *AuthHandler) Login(c echo.Context) error {
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 {
// Record failed attempt
locked, remainingTime := h.loginAttemptTracker.RecordFailedAttempt(login)
if locked {
errMsg := fmt.Sprintf("Too many failed attempts. Account locked for %d minutes", int(remainingTime.Minutes())+1)
@@ -358,10 +333,8 @@ func (h *AuthHandler) Login(c echo.Context) error {
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid credentials"})
}
// Clear failed attempts on successful login
h.loginAttemptTracker.ClearAttempts(login)
// Generate JWT with user details
accessToken, err := h.generateJWTWithAllClaims(
uuid.UUID(user.ID.Bytes).String(),
user.Role,
@@ -375,7 +348,6 @@ func (h *AuthHandler) Login(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate token"})
}
// Create refresh token
_, refreshToken, err := h.CreateRefreshToken(uuid.UUID(user.ID.Bytes))
if err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -384,9 +356,7 @@ func (h *AuthHandler) Login(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate refresh 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(`<div class="text-green-500">Login successful! Redirecting...</div>
<script>
localStorage.setItem('token', '%s');
@@ -471,60 +441,7 @@ func (h *AuthHandler) ListUsers(c echo.Context) error {
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"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Theme string `json:"theme"`
Role string `json:"role"`
MaxDevices int32 `json:"max_devices"`
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
}
firstName := ""
if u.FirstName.Valid {
firstName = u.FirstName.String
}
lastName := ""
if u.LastName.Valid {
lastName = u.LastName.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,
FirstName: firstName,
LastName: lastName,
Theme: theme,
Role: u.Role,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
return c.JSON(http.StatusOK, map[string]interface{}{"data": userList})
}
type AddEbookFolderRequest struct {
FolderPath string `json:"folder_path" validate:"required"`
return c.JSON(http.StatusOK, users)
}
// normalizePath cleans and normalizes folder paths for consistent storage and comparison
@@ -552,32 +469,6 @@ func normalizePath(path string) string {
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 (DEPRECATED - use libraries instead)
func (h *AuthHandler) AddEbookFolder(c echo.Context) error {
return c.JSON(http.StatusGone, map[string]string{"error": "Ebook folders are deprecated. Please use the library system instead."})
}
// GetEbookFolders handles GET /api/auth/ebook-folders (DEPRECATED - use libraries instead)
func (h *AuthHandler) GetEbookFolders(c echo.Context) error {
return c.JSON(http.StatusGone, map[string]string{"error": "Ebook folders are deprecated. Please use library system instead."})
}
// DeleteEbookFolder handles DELETE /api/auth/ebook-folders (DEPRECATED - use libraries instead)
func (h *AuthHandler) DeleteEbookFolder(c echo.Context) error {
return c.JSON(http.StatusGone, map[string]string{"error": "Ebook folders are deprecated. Please use library system instead."})
}
type UpdateThemeRequest struct {
Theme string `json:"theme" validate:"required"`
}