fix: resolve user registration error by fixing database queries

- Update CreateUser query to explicitly select only existing columns
- Update all user SELECT queries to explicitly select columns to avoid scan_frequency_minutes column issues
- Add GetUserForLogin query that includes password_hash for authentication
- Update login handler to use GetUserForLogin instead of GetUserByEmailOrUsername
- This prevents errors when migration hasn't been applied yet, allowing user registration to work
This commit is contained in:
2026-01-23 11:06:56 -05:00
parent a5e96ddb0b
commit fcc9b0f0b3
4 changed files with 91 additions and 34 deletions
+5 -4
View File
@@ -14,7 +14,7 @@ type Querier interface {
AddUserEbookFolder(ctx context.Context, arg AddUserEbookFolderParams) (UserEbookFolders, error) AddUserEbookFolder(ctx context.Context, arg AddUserEbookFolderParams) (UserEbookFolders, error)
CreateEbook(ctx context.Context, arg CreateEbookParams) (Ebooks, error) CreateEbook(ctx context.Context, arg CreateEbookParams) (Ebooks, error)
CreateEbookRating(ctx context.Context, arg CreateEbookRatingParams) (EbookRatings, error) CreateEbookRating(ctx context.Context, arg CreateEbookRatingParams) (EbookRatings, error)
CreateUser(ctx context.Context, arg CreateUserParams) (Users, error) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error)
DeleteEbook(ctx context.Context, id pgtype.UUID) error DeleteEbook(ctx context.Context, id pgtype.UUID) error
DeleteEbookRating(ctx context.Context, arg DeleteEbookRatingParams) error DeleteEbookRating(ctx context.Context, arg DeleteEbookRatingParams) error
DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error
@@ -27,10 +27,11 @@ type Querier interface {
GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error) GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error)
GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error) GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error)
GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error)
GetUserByEmail(ctx context.Context, email string) (Users, error) GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
GetUserByEmailOrUsername(ctx context.Context, email string) (Users, error) GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error)
GetUserByUsername(ctx context.Context, username string) (Users, error) GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error)
GetUserEbookFolders(ctx context.Context, userID pgtype.UUID) ([]UserEbookFolders, error) GetUserEbookFolders(ctx context.Context, userID pgtype.UUID) ([]UserEbookFolders, error)
GetUserForLogin(ctx context.Context, email string) (GetUserForLoginRow, error)
GetUserPasswordHash(ctx context.Context, id pgtype.UUID) (string, error) GetUserPasswordHash(ctx context.Context, id pgtype.UUID) (string, error)
ListEbooks(ctx context.Context, arg ListEbooksParams) ([]Ebooks, error) ListEbooks(ctx context.Context, arg ListEbooksParams) ([]Ebooks, error)
ListUsers(ctx context.Context) ([]ListUsersRow, error) ListUsers(ctx context.Context) ([]ListUsersRow, error)
+77 -24
View File
@@ -131,7 +131,7 @@ func (q *Queries) CreateEbookRating(ctx context.Context, arg CreateEbookRatingPa
const CreateUser = `-- name: CreateUser :one const CreateUser = `-- name: CreateUser :one
INSERT INTO users (email, username, password_hash, theme) INSERT INTO users (email, username, password_hash, theme)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)
RETURNING id, email, username, password_hash, theme, created_at, updated_at, scan_frequency_minutes, auto_scan_enabled RETURNING id, email, username, theme, created_at, updated_at
` `
type CreateUserParams struct { type CreateUserParams struct {
@@ -141,24 +141,30 @@ type CreateUserParams struct {
Theme pgtype.Text `db:"theme" json:"theme"` Theme pgtype.Text `db:"theme" json:"theme"`
} }
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (Users, error) { type CreateUserRow struct {
ID pgtype.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
Username string `db:"username" json:"username"`
Theme pgtype.Text `db:"theme" json:"theme"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) {
row := q.db.QueryRow(ctx, CreateUser, row := q.db.QueryRow(ctx, CreateUser,
arg.Email, arg.Email,
arg.Username, arg.Username,
arg.PasswordHash, arg.PasswordHash,
arg.Theme, arg.Theme,
) )
var i Users var i CreateUserRow
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash,
&i.Theme, &i.Theme,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
) )
return i, err return i, err
} }
@@ -419,64 +425,82 @@ func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, erro
} }
const GetUserByEmail = `-- name: GetUserByEmail :one const GetUserByEmail = `-- name: GetUserByEmail :one
SELECT id, email, username, password_hash, theme, created_at, updated_at, scan_frequency_minutes, auto_scan_enabled FROM users WHERE email = $1 SELECT id, email, username, theme, created_at, updated_at FROM users WHERE email = $1
` `
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (Users, error) { type GetUserByEmailRow struct {
ID pgtype.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
Username string `db:"username" json:"username"`
Theme pgtype.Text `db:"theme" json:"theme"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error) {
row := q.db.QueryRow(ctx, GetUserByEmail, email) row := q.db.QueryRow(ctx, GetUserByEmail, email)
var i Users var i GetUserByEmailRow
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash,
&i.Theme, &i.Theme,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
) )
return i, err return i, err
} }
const GetUserByEmailOrUsername = `-- name: GetUserByEmailOrUsername :one const GetUserByEmailOrUsername = `-- name: GetUserByEmailOrUsername :one
SELECT id, email, username, password_hash, theme, created_at, updated_at, scan_frequency_minutes, auto_scan_enabled FROM users WHERE email = $1 OR username = $1 SELECT id, email, username, theme, created_at, updated_at FROM users WHERE email = $1 OR username = $1
` `
func (q *Queries) GetUserByEmailOrUsername(ctx context.Context, email string) (Users, error) { type GetUserByEmailOrUsernameRow struct {
ID pgtype.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
Username string `db:"username" json:"username"`
Theme pgtype.Text `db:"theme" json:"theme"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
func (q *Queries) GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error) {
row := q.db.QueryRow(ctx, GetUserByEmailOrUsername, email) row := q.db.QueryRow(ctx, GetUserByEmailOrUsername, email)
var i Users var i GetUserByEmailOrUsernameRow
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash,
&i.Theme, &i.Theme,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
) )
return i, err return i, err
} }
const GetUserByUsername = `-- name: GetUserByUsername :one const GetUserByUsername = `-- name: GetUserByUsername :one
SELECT id, email, username, password_hash, theme, created_at, updated_at, scan_frequency_minutes, auto_scan_enabled FROM users WHERE username = $1 SELECT id, email, username, theme, created_at, updated_at FROM users WHERE username = $1
` `
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (Users, error) { type GetUserByUsernameRow struct {
ID pgtype.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
Username string `db:"username" json:"username"`
Theme pgtype.Text `db:"theme" json:"theme"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error) {
row := q.db.QueryRow(ctx, GetUserByUsername, username) row := q.db.QueryRow(ctx, GetUserByUsername, username)
var i Users var i GetUserByUsernameRow
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash,
&i.Theme, &i.Theme,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
) )
return i, err return i, err
} }
@@ -510,6 +534,35 @@ func (q *Queries) GetUserEbookFolders(ctx context.Context, userID pgtype.UUID) (
return items, nil return items, nil
} }
const GetUserForLogin = `-- name: GetUserForLogin :one
SELECT id, email, username, password_hash, theme, created_at, updated_at FROM users WHERE email = $1 OR username = $1
`
type GetUserForLoginRow struct {
ID pgtype.UUID `db:"id" json:"id"`
Email string `db:"email" json:"email"`
Username string `db:"username" json:"username"`
PasswordHash string `db:"password_hash" json:"password_hash"`
Theme pgtype.Text `db:"theme" json:"theme"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
func (q *Queries) GetUserForLogin(ctx context.Context, email string) (GetUserForLoginRow, error) {
row := q.db.QueryRow(ctx, GetUserForLogin, email)
var i GetUserForLoginRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.Theme,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetUserPasswordHash = `-- name: GetUserPasswordHash :one const GetUserPasswordHash = `-- name: GetUserPasswordHash :one
SELECT password_hash FROM users WHERE id = $1 SELECT password_hash FROM users WHERE id = $1
` `
+7 -4
View File
@@ -1,16 +1,19 @@
-- name: CreateUser :one -- name: CreateUser :one
INSERT INTO users (email, username, password_hash, theme) INSERT INTO users (email, username, password_hash, theme)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)
RETURNING *; RETURNING id, email, username, theme, created_at, updated_at;
-- name: GetUserByEmail :one -- name: GetUserByEmail :one
SELECT * FROM users WHERE email = $1; SELECT id, email, username, theme, created_at, updated_at FROM users WHERE email = $1;
-- name: GetUserByUsername :one -- name: GetUserByUsername :one
SELECT * FROM users WHERE username = $1; SELECT id, email, username, theme, created_at, updated_at FROM users WHERE username = $1;
-- name: GetUserByEmailOrUsername :one -- name: GetUserByEmailOrUsername :one
SELECT * FROM users WHERE email = $1 OR username = $1; SELECT id, email, username, theme, created_at, updated_at FROM users WHERE email = $1 OR username = $1;
-- name: GetUserForLogin :one
SELECT id, email, username, password_hash, theme, created_at, updated_at FROM users WHERE email = $1 OR username = $1;
-- name: GetUser :one -- name: GetUser :one
SELECT id, email, username, theme, created_at, updated_at FROM users WHERE id = $1; SELECT id, email, username, theme, created_at, updated_at FROM users WHERE id = $1;
+2 -2
View File
@@ -204,8 +204,8 @@ func (h *AuthHandler) Login(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
} }
// Get user by email or username // Get user by email or username (includes password hash for verification)
user, err := h.db.GetUserByEmailOrUsername(c.Request().Context(), req.Login) user, err := h.db.GetUserForLogin(c.Request().Context(), req.Login)
if err != nil { if err != nil {
if c.Request().Header.Get("HX-Request") == "true" { if c.Request().Header.Get("HX-Request") == "true" {
return c.HTML(http.StatusUnauthorized, `<div class="text-red-500">Invalid credentials</div>`) return c.HTML(http.StatusUnauthorized, `<div class="text-red-500">Invalid credentials</div>`)