176 lines
5.8 KiB
Go
176 lines
5.8 KiB
Go
package userstore
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
pgtable "galaxy/user/internal/adapters/postgres/jet/user/table"
|
|
"galaxy/user/internal/domain/authblock"
|
|
"galaxy/user/internal/domain/common"
|
|
"galaxy/user/internal/ports"
|
|
|
|
pg "github.com/go-jet/jet/v2/postgres"
|
|
)
|
|
|
|
// blockedEmailSelectColumns is the canonical SELECT list for blocked_emails.
|
|
var blockedEmailSelectColumns = pg.ColumnList{
|
|
pgtable.BlockedEmails.Email,
|
|
pgtable.BlockedEmails.ReasonCode,
|
|
pgtable.BlockedEmails.BlockedAt,
|
|
pgtable.BlockedEmails.ActorType,
|
|
pgtable.BlockedEmails.ActorID,
|
|
pgtable.BlockedEmails.ResolvedUserID,
|
|
}
|
|
|
|
// GetBlockedEmail returns the blocked-email subject for email.
|
|
func (store *Store) GetBlockedEmail(ctx context.Context, email common.Email) (authblock.BlockedEmailSubject, error) {
|
|
if err := email.Validate(); err != nil {
|
|
return authblock.BlockedEmailSubject{}, fmt.Errorf("get blocked email subject from postgres: %w", err)
|
|
}
|
|
|
|
operationCtx, cancel, err := store.operationContext(ctx, "get blocked email subject from postgres")
|
|
if err != nil {
|
|
return authblock.BlockedEmailSubject{}, err
|
|
}
|
|
defer cancel()
|
|
|
|
record, err := scanBlockedEmail(operationCtx, store.db, email, false)
|
|
switch {
|
|
case errors.Is(err, ports.ErrNotFound):
|
|
return authblock.BlockedEmailSubject{}, fmt.Errorf("get blocked email subject %q from postgres: %w", email, ports.ErrNotFound)
|
|
case err != nil:
|
|
return authblock.BlockedEmailSubject{}, fmt.Errorf("get blocked email subject %q from postgres: %w", email, err)
|
|
}
|
|
return record, nil
|
|
}
|
|
|
|
// PutBlockedEmail stores or replaces the blocked-email subject for
|
|
// record.Email. The schema's PRIMARY KEY on (email) makes this an UPSERT via
|
|
// `INSERT … ON CONFLICT (email) DO UPDATE`.
|
|
func (store *Store) PutBlockedEmail(ctx context.Context, record authblock.BlockedEmailSubject) error {
|
|
if err := record.Validate(); err != nil {
|
|
return fmt.Errorf("upsert blocked email subject in postgres: %w", err)
|
|
}
|
|
|
|
operationCtx, cancel, err := store.operationContext(ctx, "upsert blocked email subject in postgres")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cancel()
|
|
|
|
if err := upsertBlockedEmail(operationCtx, store.db, record); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// upsertBlockedEmail centralises the UPSERT used by PutBlockedEmail and the
|
|
// composite block flows. q is a *sql.DB or *sql.Tx so it can run inside an
|
|
// auth-directory transaction.
|
|
func upsertBlockedEmail(ctx context.Context, q queryer, record authblock.BlockedEmailSubject) error {
|
|
stmt := pgtable.BlockedEmails.INSERT(
|
|
pgtable.BlockedEmails.Email,
|
|
pgtable.BlockedEmails.ReasonCode,
|
|
pgtable.BlockedEmails.BlockedAt,
|
|
pgtable.BlockedEmails.ActorType,
|
|
pgtable.BlockedEmails.ActorID,
|
|
pgtable.BlockedEmails.ResolvedUserID,
|
|
).VALUES(
|
|
record.Email.String(),
|
|
record.ReasonCode.String(),
|
|
record.BlockedAt.UTC(),
|
|
nullableActorType(record.Actor.Type),
|
|
nullableActorID(record.Actor.ID),
|
|
nullableUserID(record.ResolvedUserID),
|
|
).ON_CONFLICT(pgtable.BlockedEmails.Email).DO_UPDATE(
|
|
pg.SET(
|
|
pgtable.BlockedEmails.ReasonCode.SET(pgtable.BlockedEmails.EXCLUDED.ReasonCode),
|
|
pgtable.BlockedEmails.BlockedAt.SET(pgtable.BlockedEmails.EXCLUDED.BlockedAt),
|
|
pgtable.BlockedEmails.ActorType.SET(pgtable.BlockedEmails.EXCLUDED.ActorType),
|
|
pgtable.BlockedEmails.ActorID.SET(pgtable.BlockedEmails.EXCLUDED.ActorID),
|
|
pgtable.BlockedEmails.ResolvedUserID.SET(pgtable.BlockedEmails.EXCLUDED.ResolvedUserID),
|
|
),
|
|
)
|
|
|
|
query, args := stmt.Sql()
|
|
if _, err := q.ExecContext(ctx, query, args...); err != nil {
|
|
return fmt.Errorf("upsert blocked email subject %q in postgres: %w", record.Email, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// scanBlockedEmail loads one blocked-email row. forUpdate selects the
|
|
// `FOR UPDATE` lock variant used inside the auth-directory transaction.
|
|
func scanBlockedEmail(ctx context.Context, q queryer, email common.Email, forUpdate bool) (authblock.BlockedEmailSubject, error) {
|
|
stmt := pg.SELECT(blockedEmailSelectColumns).
|
|
FROM(pgtable.BlockedEmails).
|
|
WHERE(pgtable.BlockedEmails.Email.EQ(pg.String(email.String())))
|
|
if forUpdate {
|
|
stmt = stmt.FOR(pg.UPDATE())
|
|
}
|
|
query, args := stmt.Sql()
|
|
row := q.QueryRowContext(ctx, query, args...)
|
|
return scanBlockedEmailRow(row)
|
|
}
|
|
|
|
func scanBlockedEmailRow(row *sql.Row) (authblock.BlockedEmailSubject, error) {
|
|
var (
|
|
record authblock.BlockedEmailSubject
|
|
emailValue string
|
|
reasonCode string
|
|
blockedAt time.Time
|
|
actorType *string
|
|
actorID *string
|
|
resolvedUserID *string
|
|
)
|
|
if err := row.Scan(
|
|
&emailValue, &reasonCode, &blockedAt,
|
|
&actorType, &actorID, &resolvedUserID,
|
|
); err != nil {
|
|
return authblock.BlockedEmailSubject{}, mapNotFound(err)
|
|
}
|
|
|
|
record.Email = common.Email(emailValue)
|
|
record.ReasonCode = common.ReasonCode(reasonCode)
|
|
record.BlockedAt = blockedAt.UTC()
|
|
if actorType != nil {
|
|
record.Actor.Type = common.ActorType(*actorType)
|
|
}
|
|
if actorID != nil {
|
|
record.Actor.ID = common.ActorID(*actorID)
|
|
}
|
|
if resolvedUserID != nil {
|
|
record.ResolvedUserID = common.UserID(*resolvedUserID)
|
|
}
|
|
return record, nil
|
|
}
|
|
|
|
// BlockedEmailStore adapts Store to the BlockedEmailStore port.
|
|
type BlockedEmailStore struct {
|
|
store *Store
|
|
}
|
|
|
|
// BlockedEmails returns one adapter that exposes the blocked-email store
|
|
// port over Store.
|
|
func (store *Store) BlockedEmails() *BlockedEmailStore {
|
|
if store == nil {
|
|
return nil
|
|
}
|
|
return &BlockedEmailStore{store: store}
|
|
}
|
|
|
|
// GetByEmail returns the blocked-email subject for email.
|
|
func (adapter *BlockedEmailStore) GetByEmail(ctx context.Context, email common.Email) (authblock.BlockedEmailSubject, error) {
|
|
return adapter.store.GetBlockedEmail(ctx, email)
|
|
}
|
|
|
|
// Upsert stores or replaces the blocked-email subject for record.Email.
|
|
func (adapter *BlockedEmailStore) Upsert(ctx context.Context, record authblock.BlockedEmailSubject) error {
|
|
return adapter.store.PutBlockedEmail(ctx, record)
|
|
}
|
|
|
|
var _ ports.BlockedEmailStore = (*BlockedEmailStore)(nil)
|