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)
CreateEbook(ctx context.Context, arg CreateEbookParams) (Ebooks, 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
DeleteEbookRating(ctx context.Context, arg DeleteEbookRatingParams) error
DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error
@@ -27,10 +27,11 @@ type Querier interface {
GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error)
GetScanSettings(ctx context.Context, id pgtype.UUID) (GetScanSettingsRow, error)
GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error)
GetUserByEmail(ctx context.Context, email string) (Users, error)
GetUserByEmailOrUsername(ctx context.Context, email string) (Users, error)
GetUserByUsername(ctx context.Context, username string) (Users, error)
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error)
GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, 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)
ListEbooks(ctx context.Context, arg ListEbooksParams) ([]Ebooks, 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
INSERT INTO users (email, username, password_hash, theme)
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 {
@@ -141,24 +141,30 @@ type CreateUserParams struct {
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,
arg.Email,
arg.Username,
arg.PasswordHash,
arg.Theme,
)
var i Users
var i CreateUserRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.Theme,
&i.CreatedAt,
&i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
)
return i, err
}
@@ -419,64 +425,82 @@ func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, erro
}
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)
var i Users
var i GetUserByEmailRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.Theme,
&i.CreatedAt,
&i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
)
return i, err
}
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)
var i Users
var i GetUserByEmailOrUsernameRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.Theme,
&i.CreatedAt,
&i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
)
return i, err
}
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)
var i Users
var i GetUserByUsernameRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.Theme,
&i.CreatedAt,
&i.UpdatedAt,
&i.ScanFrequencyMinutes,
&i.AutoScanEnabled,
)
return i, err
}
@@ -510,6 +534,35 @@ func (q *Queries) GetUserEbookFolders(ctx context.Context, userID pgtype.UUID) (
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
SELECT password_hash FROM users WHERE id = $1
`
+7 -4
View File
@@ -1,16 +1,19 @@
-- name: CreateUser :one
INSERT INTO users (email, username, password_hash, theme)
VALUES ($1, $2, $3, $4)
RETURNING *;
RETURNING id, email, username, theme, created_at, updated_at;
-- 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
SELECT * FROM users WHERE username = $1;
SELECT id, email, username, theme, created_at, updated_at FROM users WHERE username = $1;
-- 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
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()})
}
// Get user by email or username
user, err := h.db.GetUserByEmailOrUsername(c.Request().Context(), req.Login)
// 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, `<div class="text-red-500">Invalid credentials</div>`)