Files
spsays-bff/internal/repository/oauth_account_repo.go
2026-06-02 22:54:41 +05:30

95 lines
3.2 KiB
Go

package repository
import (
"context"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/prabhupadasays/spsays-bff/internal/model"
)
// ErrOAuthAccountNotFound mirrors the other repo sentinels.
var ErrOAuthAccountNotFound = errors.New("oauth account not found")
// OAuthAccountRepo manages provider linkage rows.
type OAuthAccountRepo struct {
pg *Postgres
}
func NewOAuthAccountRepo(pg *Postgres) *OAuthAccountRepo {
return &OAuthAccountRepo{pg: pg}
}
// FindByProvider returns the linkage row for the given (provider, provider_user_id),
// or ErrOAuthAccountNotFound. Use this first on every callback — a hit means
// this provider identity has been seen before and we just log the user in.
func (r *OAuthAccountRepo) FindByProvider(ctx context.Context, provider, providerUserID string) (*model.OAuthAccount, error) {
const q = `
SELECT id, user_id, provider, provider_user_id, email, name, avatar_url, created_at, updated_at
FROM oauth_accounts
WHERE provider = $1 AND provider_user_id = $2
LIMIT 1`
a := &model.OAuthAccount{}
err := r.pg.Pool.QueryRow(ctx, q, provider, providerUserID).Scan(
&a.ID, &a.UserID, &a.Provider, &a.ProviderUserID,
&a.Email, &a.Name, &a.AvatarURL, &a.CreatedAt, &a.UpdatedAt,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrOAuthAccountNotFound
}
return nil, fmt.Errorf("find oauth account: %w", err)
}
return a, nil
}
// ListProvidersForUser returns the distinct provider names linked to this
// user, ordered alphabetically. Used by the settings UI to mark each
// social-connect row as "Connected" vs "Connect".
func (r *OAuthAccountRepo) ListProvidersForUser(ctx context.Context, userID string) ([]string, error) {
const q = `
SELECT DISTINCT provider
FROM oauth_accounts
WHERE user_id = $1
ORDER BY provider`
rows, err := r.pg.Pool.Query(ctx, q, userID)
if err != nil {
return nil, fmt.Errorf("list oauth providers: %w", err)
}
defer rows.Close()
out := []string{}
for rows.Next() {
var p string
if err := rows.Scan(&p); err != nil {
return nil, fmt.Errorf("scan oauth provider: %w", err)
}
out = append(out, p)
}
return out, rows.Err()
}
// Link inserts a new oauth_accounts row tying a provider identity to an
// existing user. Idempotent: a second call for the same (provider, sub)
// returns the existing row instead of erroring on the unique constraint.
func (r *OAuthAccountRepo) Link(ctx context.Context, userID, provider, providerUserID string, email, name, avatarURL *string) (*model.OAuthAccount, error) {
const q = `
INSERT INTO oauth_accounts (user_id, provider, provider_user_id, email, name, avatar_url)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (provider, provider_user_id) DO UPDATE
SET email = EXCLUDED.email,
name = EXCLUDED.name,
avatar_url = EXCLUDED.avatar_url,
updated_at = now()
RETURNING id, user_id, provider, provider_user_id, email, name, avatar_url, created_at, updated_at`
a := &model.OAuthAccount{}
err := r.pg.Pool.QueryRow(ctx, q, userID, provider, providerUserID, email, name, avatarURL).Scan(
&a.ID, &a.UserID, &a.Provider, &a.ProviderUserID,
&a.Email, &a.Name, &a.AvatarURL, &a.CreatedAt, &a.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("link oauth account: %w", err)
}
return a, nil
}