package accounts // to generate StoreWithContext //go:generate concurry -s Store 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 } Account struct { AccountIDs OAuthUser Email string } OAuthUser struct { UserID string } AccountShop struct { AccountShopIDs Name string } Listing struct { AccountShopListingIDs SKU string Name string Description string Count int64 } // ids AccountIDs struct { AccountID int64 } AccountShopIDs struct { AccountIDs Platform Platform ShopID string } AccountShopListingIDs struct { AccountShopIDs ListingID string } ) func NewStore(db *pgxpool.Pool) *Store { return &Store{ db: db, } } func (db *Store) WithContext(ctx context.Context) *StoreWithContext { return NewStoreWithContext(ctx, 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{ OAuthUser: OAuthUser{ UserID: userID, }, AccountIDs: AccountIDs{ AccountID: acctID, }, Email: email, }, nil } func (db *Store) GetAccount(ctx context.Context, id int64) (Account, error) { rows, err := db.db.Query( ctx, "SELECT email, user_id 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) } type Row struct { Email string User_id string } 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{ AccountIDs: AccountIDs{ AccountID: id, }, OAuthUser: OAuthUser{ r.User_id, }, Email: r.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{ AccountIDs: AccountIDs{ AccountID: r.Account_ID, }, OAuthUser: OAuthUser{ UserID: userID, }, Email: r.Email, }, nil } func (db *Store) GetAccountByEmail(ctx context.Context, email string) (Account, error) { rows, err := db.db.Query( ctx, "SELECT account_id, user_id FROM accounts WHERE email = @email", pgx.NamedArgs{ "email": email, }, ) if err != nil { return Account{}, fmt.Errorf("failed to perform query: %w", err) } type Row struct { Account_id int64 User_id string } 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{ AccountIDs: AccountIDs{ AccountID: r.Account_id, }, OAuthUser: OAuthUser{ UserID: r.User_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) } user := OAuthUser{UserID: r.User_ID} var acct *Account if r.Account_ID != nil { acct = &Account{ AccountIDs: AccountIDs{ AccountID: *r.Account_ID, }, OAuthUser: user, Email: *r.Email, } } return user, acct, nil } func (db *Store) GetShops(ctx context.Context, acctID int64) ([]AccountShop, error) { var shops []AccountShop for _, v := range devShops { if v.AccountID != acctID { continue } shops = append(shops, v) } return shops, nil } func (db *Store) GetListingsForShop(ctx context.Context, acctID int64, platform Platform, shopID string) ([]Listing, error) { var vs []Listing for _, v := range devListings { if v.AccountID != acctID { continue } if v.Platform != platform { continue } if v.ShopID != shopID { continue } vs = append(vs, v) } return vs, nil } // additional context func (db *Store) GetAccountPointerByUserID(ctx context.Context, userID string) (*Account, error) { acct, err := db.GetAccountByUserID(ctx, userID) if err == nil { return &acct, nil } if errors.Is(err, consts.ErrNotFound) { return nil, nil } return nil, err }