receipt_indexer/backend/internal/storage/sql-users.go
2025-05-05 22:08:06 -04:00

232 lines
5.1 KiB
Go

package storage
import (
"context"
"database/sql"
"fmt"
"time"
)
type SQLUsersStore struct {
db *sql.DB
}
func (s *SQLUsersStore) GetByID(ctx context.Context, id int64) (*User, error) {
query := `SELECT users.id, users.username, users.email, users.password, users.created_at, roles.*
FROM users
JOIN roles ON (users.role_id = roles.id)
WHERE users.id = $1 AND is_active = true`
ctx, cancel := context.WithTimeout(ctx, QueryTimeoutDuration)
defer cancel()
user := &User{}
err := s.db.QueryRowContext(
ctx,
query,
id,
).Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Password.hash,
&user.CreatedAt,
&user.Role.ID,
&user.Role.Name,
&user.Role.Level,
&user.Role.Description,
)
if err != nil {
switch err {
case sql.ErrNoRows:
return nil, ErrNotFound
default:
return nil, err
}
}
return user, nil
}
func (s *SQLUsersStore) GetByEmail(ctx context.Context, email string) (*User, error) {
query := `SELECT users.id, users.username, users.email, users.password, users.created_at, roles.*
FROM users
JOIN roles ON (users.role_id = roles.id)
WHERE users.email = $1 AND is_active = true`
ctx, cancel := context.WithTimeout(ctx, QueryTimeoutDuration)
defer cancel()
user := &User{}
err := s.db.QueryRowContext(
ctx,
query,
email,
).Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Password.hash,
&user.CreatedAt,
&user.Role.ID,
&user.Role.Name,
&user.Role.Level,
&user.Role.Description,
)
if err != nil {
switch err {
case sql.ErrNoRows:
return nil, ErrNotFound
default:
return nil, err
}
}
return user, nil
}
func (s *SQLUsersStore) GetByUsername(ctx context.Context, username string) (*User, error) {
query := `SELECT users.id, users.username, users.email, users.password, users.created_at, roles.*
FROM users
JOIN roles ON (users.role_id = roles.id)
WHERE users.username = $1 AND is_active = true`
ctx, cancel := context.WithTimeout(ctx, QueryTimeoutDuration)
defer cancel()
user := &User{}
err := s.db.QueryRowContext(
ctx,
query,
username,
).Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Password.hash,
&user.CreatedAt,
&user.Role.ID,
&user.Role.Name,
&user.Role.Level,
&user.Role.Description,
)
if err != nil {
switch err {
case sql.ErrNoRows:
return nil, ErrNotFound
default:
return nil, err
}
}
return user, nil
}
func (s *SQLUsersStore) create(ctx context.Context, user *User, tx *sql.Tx) error { // creates the personal group and binds them
ctx, cancel := context.WithTimeout(ctx, QueryTimeoutDuration)
defer cancel()
query := `INSERT INTO groups (name, owner) VALUES ($1, $2) RETURNING id`
err := tx.QueryRowContext(ctx, query, fmt.Sprintf("User-%d-Personal-Group", user.ID), user.ID).Scan(&user.PersonalGroup)
if err != nil {
return err
}
query = `INSERT INTO users (username, password, email, role_id, personalgroup) VALUES
($1, $2, $3, (SELECT id FROM roles WHERE name = $4), $5)
RETURNING id, created_at`
role := user.Role.Name
if role == "" {
role = "user"
}
err = tx.QueryRowContext(
ctx,
query,
user.Username,
user.Password,
user.Email,
role,
user.PersonalGroup,
).Scan(
&user.ID,
&user.CreatedAt,
)
if err != nil {
switch {
case err.Error() == `pq: duplicate key value violates unique constraint "users_email_key"`:
return ErrDuplicateEmail
case err.Error() == `pq: duplicate key value violates unique constraint "users_username_key"`:
return ErrDuplicateUsername
default:
return err
}
}
query = `INSERT INTO groupMembership (groupid, userid, moderator) VALUES ($1, $2, $3)`
_, err = tx.ExecContext(ctx, query, user.PersonalGroup, user.ID, true)
if err != nil {
return err
}
return nil
}
func (s *SQLUsersStore) Create(ctx context.Context, user *User) error { // create a non-exported create function which does take in the tx
return withTx(s.db, ctx, func(tx *sql.Tx) error {
return s.create(ctx, user, tx)
})
}
func (s *SQLUsersStore) CreateAndInvite(ctx context.Context, user *User, token string, exp time.Duration) error { // figure this out
return nil
}
func (s *SQLUsersStore) Activate(context.Context, string) error { // what does this do?
return nil
}
func (s *SQLUsersStore) delete(ctx context.Context, id int64, tx *sql.Tx) error {
query := `DELETE FROM users WHERE id = $1`
ctx, cancel := context.WithTimeout(ctx, QueryTimeoutDuration)
defer cancel()
res, err := s.db.ExecContext(ctx, query, id)
if err != nil {
return err
}
rows, err := res.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return ErrNotFound
}
return nil
}
func (s *SQLUsersStore) Delete(ctx context.Context, id int64) error {
return withTx(s.db, ctx, func(tx *sql.Tx) error {
return s.delete(ctx, id, tx)
})
}
func (s *SQLUsersStore) UpdateUserPass(ctx context.Context, user string, oldPassword string, newPass string) error {
return nil
}
func (s *SQLUsersStore) CheckPass(ctx context.Context, name string, pass string) (bool, error) {
return false, nil
}
func (s *SQLUsersStore) SigninUser(ctx context.Context, name string, pass string) (bool, *User, error) {
return false, nil, nil
}