package db
import (
"database/sql"
"errors"
"fmt"
)
// ErrNotFound is returned when a lookup by key matches no row.
var ErrNotFound = errors.New("not found")
// User is a row in the users table.
type User struct {
ID int64
Username string
PasswordHash string
}
// CreateUser inserts a user and returns its id.
func CreateUser(database *sql.DB, username, passwordHash string) (int64, error) {
id, err := execLastID(database,
`INSERT INTO users (username, password_hash) VALUES (?, ?)`, username, passwordHash)
if err != nil {
return 0, fmt.Errorf("create user %s: %w", username, err)
}
return id, nil
}
// scanUser reads one users row into user.
func scanUser(s rowScanner, user *User) error {
return s.Scan(&user.ID, &user.Username, &user.PasswordHash)
}
// GetUserByName looks up a user by their unique username.
func GetUserByName(database *sql.DB, username string) (User, error) {
var user User
err := scanUser(database.QueryRow(
`SELECT id, username, password_hash FROM users WHERE username = ?`, username), &user)
if errors.Is(err, sql.ErrNoRows) {
return User{}, fmt.Errorf("user %s: %w", username, ErrNotFound)
}
if err != nil {
return User{}, fmt.Errorf("get user %s: %w", username, err)
}
return user, nil
}
// GetUserByID looks up a user by primary key.
func GetUserByID(database *sql.DB, id int64) (User, error) {
var user User
err := scanUser(database.QueryRow(
`SELECT id, username, password_hash FROM users WHERE id = ?`, id), &user)
if errors.Is(err, sql.ErrNoRows) {
return User{}, fmt.Errorf("user %d: %w", id, ErrNotFound)
}
if err != nil {
return User{}, fmt.Errorf("get user %d: %w", id, err)
}
return user, nil
}
// UpdateUserPassword replaces a user's password hash.
func UpdateUserPassword(database *sql.DB, id int64, passwordHash string) error {
return execScoped(database, fmt.Sprintf("update password for user %d", id),
`UPDATE users SET password_hash = ? WHERE id = ?`, passwordHash, id)
}