josie / simplegit

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)
}