package accounts import ( "context" "errors" "fmt" "ruben/inventory2/internal/consts" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) type ( Store struct { db *pgxpool.Pool } StoreWithContext struct { ctx context.Context db *Store } Account struct { UserID string ID int64 Email string } OAuthUser struct { UserID string } ) func NewStore(db *pgxpool.Pool) *Store { return &Store{ db: db, } } func (db *Store) WithContext(ctx context.Context) *StoreWithContext { return &StoreWithContext{ ctx: ctx, db: db, } } func (db *Store) CreateAccount(ctx context.Context, userID, email string) (Account, error) { rows, err := db.db.Query( ctx, ` INSERT INTO accounts ( user_id, email ) VALUES ( @user_id, @email ) ON CONFLICT DO NOTHING RETURNING account_id `, pgx.NamedArgs{ "user_id": userID, "email": email, }, ) if err != nil { return Account{}, fmt.Errorf("failed to perform query: %w", err) } acctID, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int64]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return Account{}, fmt.Errorf("account already exists: %w", consts.ErrConflict) } return Account{}, fmt.Errorf("failed to scan row: %w", err) } return Account{ UserID: userID, ID: acctID, Email: email, }, nil } func (db *Store) GetAccount(ctx context.Context, id int64) (Account, error) { rows, err := db.db.Query( ctx, "SELECT email FROM accounts WHERE account_id = @account_id", pgx.NamedArgs{ "account_id": id, }, ) if err != nil { return Account{}, fmt.Errorf("failed to perform query: %w", err) } email, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[string]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return Account{}, consts.ErrNotFound } return Account{}, fmt.Errorf("failed to scan row: %w", err) } return Account{ ID: id, Email: email, }, nil } func (db *Store) GetAccountByUserID(ctx context.Context, userID string) (Account, error) { rows, err := db.db.Query( ctx, `SELECT email, account_id FROM accounts WHERE user_id = @user_id`, pgx.NamedArgs{ "user_id": userID, }, ) if err != nil { return Account{}, fmt.Errorf("failed to perform query: %w", err) } type Row struct { Email string Account_ID int64 } r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return Account{}, consts.ErrNotFound } return Account{}, fmt.Errorf("failed to scan row: %w", err) } return Account{ UserID: userID, ID: r.Account_ID, Email: r.Email, }, nil } func (db *Store) GetAccountByEmail(ctx context.Context, email string) (Account, error) { rows, err := db.db.Query( ctx, "SELECT account_id FROM accounts WHERE email = @email", pgx.NamedArgs{ "email": email, }, ) if err != nil { return Account{}, fmt.Errorf("failed to perform query: %w", err) } id, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int64]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return Account{}, consts.ErrNotFound } return Account{}, fmt.Errorf("failed to scan row: %w", err) } return Account{ ID: id, Email: email, }, nil } func (db *Store) GetUserAndAccountByAccessToken(ctx context.Context, accessToken string) (OAuthUser, *Account, error) { rows, err := db.db.Query( ctx, ` SELECT u.user_id, a.account_id, a.email FROM oauth_users u LEFT JOIN oauth_tokens t ON u.user_id = id_token_subject LEFT JOIN accounts a USING (user_id) WHERE access_token = @access_token `, pgx.NamedArgs{ "access_token": accessToken, }, ) if err != nil { return OAuthUser{}, nil, fmt.Errorf("failed to perform query: %w", err) } type Row struct { User_ID string Account_ID *int64 Email *string } r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return OAuthUser{}, nil, consts.ErrNotFound } return OAuthUser{}, nil, fmt.Errorf("failed to scan row: %w", err) } var acct *Account if r.Account_ID != nil { acct = &Account{ UserID: r.User_ID, ID: *r.Account_ID, Email: *r.Email, } } return OAuthUser{UserID: r.User_ID}, acct, nil } func (db *StoreWithContext) CreateAccount(userID, email string) (Account, error) { return db.db.CreateAccount(db.ctx, userID, email) } func (db *StoreWithContext) GetAccount(id int64) (Account, error) { return db.db.GetAccount(db.ctx, id) } func (db *StoreWithContext) GetAccountPointerByUserID(userID string) (*Account, error) { acct, err := db.db.GetAccountByUserID(db.ctx, userID) if err == nil { return &acct, nil } if errors.Is(err, consts.ErrNotFound) { return nil, nil } return nil, err }