2026-02-07 23:41:31 -07:00

127 lines
3.7 KiB
Go

package repository
import (
"database/sql"
"fmt"
sq "github.com/Masterminds/squirrel"
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type UserRepository struct {
db *sqlx.DB
}
// Compile-time check to ensure the UserRepository implements domain.UserRepository
var _ domain.UserRepository = (*UserRepository)(nil)
// NewUserRepository creates a user repository object which is used by the user service to access
// the database. Any user related database operations will take place in this repository.
func NewUserRepository(db *sqlx.DB) domain.UserRepository {
return &UserRepository{db: db}
}
// CreateGoogleUser creates a user entry in the users table. The refresh token is required along
// with the Google user info, in order to complete the database schema. Currently, Google login is
// the only support method of authentication, if this changes, this repository may need updates to
// match an updated table schema.
//
// This function will NOT check if the user already exists, if they do, it will return an error. For
// best results, pair this function with the GetGoogleUser which will return the user if it can find
// it.
func (r *UserRepository) CreateGoogleUser(googleUserInfo *domain.GoogleUserInfo, googleRefreshToken string) (domain.User, error) {
if googleUserInfo == nil {
return domain.User{}, fmt.Errorf("Google user info provided was nil")
}
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
query := psql.
Insert("users").
Columns(
"googleid",
"name",
"email",
"imageurl",
"googlerefreshtoken").
Values(
googleUserInfo.Id,
googleUserInfo.Name,
googleUserInfo.Email,
googleUserInfo.Picture,
googleRefreshToken,
).
Suffix("RETURNING *")
_sql, args, err := query.ToSql()
if err != nil {
return domain.User{}, fmt.Errorf("Failed to construct sql query: %w", err)
}
var user domain.User
if err := r.db.Get(&user, _sql, args...); err != nil {
return domain.User{}, fmt.Errorf("Failed to create user: %w", err)
}
return user, nil
}
// GetGoogleUser attempts to find a user in the database via its Google ID, not the database ID. This
// function is used when a user logs in with Google to prevent duplicate entries from being made. If
// no user is found, this function will return a null pointer but not an error.
func (r *UserRepository) GetGoogleUser(googleId string) (*domain.User, error) {
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
query := psql.
Select("*").
From("users").
Where(sq.Eq{
"GoogleId": googleId,
})
_sql, args, err := query.ToSql()
if err != nil {
return nil, fmt.Errorf("Failed to construct sql query: %w", err)
}
var user domain.User
if err := r.db.Get(&user, _sql, args...); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, fmt.Errorf("Failed to get Google user: %w", err)
}
return &user, nil
}
// GetUser gets a user from the database via its ID. The operation is wrapped in a transaction
// for added safety. The repository will not check for a nil result, instead the service will.
// Callers are responsible for protecting against double nil results. Any errors will be bubbled
// to the caller.
func (r *UserRepository) GetUser(id int) (*domain.User, error) {
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
query := psql.
Select("*").
From("users").
Where(sq.Eq{"id": id})
_sql, args, err := query.ToSql()
if err != nil {
return nil, fmt.Errorf("Failed to construct sql query: %w", err)
}
var user domain.User
if err := r.db.Get(&user, _sql, args...); err != nil {
// If no user was found, don't error, just return
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return &user, nil
}