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 }