feat: use postgres

This commit is contained in:
Ilia Denisov
2026-04-26 20:34:39 +02:00
committed by GitHub
parent 48b0056b49
commit fe829285a6
365 changed files with 29223 additions and 24049 deletions
@@ -0,0 +1,175 @@
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)