Partial implementation of the SQL version of the user storage interface

Signed-off-by: Ethan Wellenreiter <ewellenreiter@gmail.com>
This commit is contained in:
Ethan Wellenreiter 2025-05-05 22:07:37 -04:00
parent fc11937b07
commit 970cf0274e

View File

@ -3,6 +3,7 @@ package storage
import (
"context"
"database/sql"
"fmt"
"time"
)
@ -11,21 +12,175 @@ type SQLUsersStore struct {
}
func (s *SQLUsersStore) GetByID(ctx context.Context, id int64) (*User, error) {
return nil, nil
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(context.Context, string) (*User, error) {
return nil, 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(context.Context, string) (*User, error) {
return nil, 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(context.Context, *User) error { // create a non-exported create function which does take in the tx
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
}
@ -34,10 +189,35 @@ func (s *SQLUsersStore) Activate(context.Context, string) error { // what does t
return nil
}
func (s *SQLUsersStore) Delete(ctx context.Context, id int64) error {
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
}