docs: reorder & testing

This commit is contained in:
Ilia Denisov
2026-05-07 00:58:53 +03:00
committed by GitHub
parent f446c6a2ac
commit 604fe40bcf
148 changed files with 9150 additions and 2757 deletions
+108 -49
View File
@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"time"
"galaxy/backend/internal/postgres/jet/backend/model"
@@ -72,8 +73,7 @@ type sanctionInsert struct {
SanctionCode string
Scope string
ReasonCode string
ActorType string
ActorID string
Actor ActorRef
AppliedAt time.Time
ExpiresAt *time.Time
FlipPermanent bool
@@ -85,8 +85,7 @@ type limitInsert struct {
LimitCode string
Value int32
ReasonCode string
ActorType string
ActorID string
Actor ActorRef
AppliedAt time.Time
ExpiresAt *time.Time
}
@@ -113,7 +112,8 @@ func accountColumns() postgres.ColumnList {
func snapshotColumns() postgres.ColumnList {
s := table.EntitlementSnapshots
return postgres.ColumnList{
s.UserID, s.Tier, s.IsPaid, s.Source, s.ActorType, s.ActorID,
s.UserID, s.Tier, s.IsPaid, s.Source,
s.ActorType, s.ActorUserID, s.ActorUsername,
s.ReasonCode, s.StartsAt, s.EndsAt, s.MaxRegisteredRaceNames, s.UpdatedAt,
}
}
@@ -275,7 +275,7 @@ func (s *Store) ListActiveSanctions(ctx context.Context, userID uuid.UUID) ([]Ac
r := table.SanctionRecords
stmt := postgres.SELECT(
r.SanctionCode, r.Scope, r.ReasonCode,
r.ActorType, r.ActorID,
r.ActorType, r.ActorUserID, r.ActorUsername,
r.AppliedAt, r.ExpiresAt,
).
FROM(a.INNER_JOIN(r, r.RecordID.EQ(a.RecordID))).
@@ -292,7 +292,7 @@ func (s *Store) ListActiveSanctions(ctx context.Context, userID uuid.UUID) ([]Ac
SanctionCode: row.SanctionCode,
Scope: row.Scope,
ReasonCode: row.ReasonCode,
Actor: ActorRef{Type: row.ActorType, ID: derefString(row.ActorID)},
Actor: actorFromColumns(row.ActorType, row.ActorUserID, row.ActorUsername),
AppliedAt: row.AppliedAt,
}
if row.ExpiresAt != nil {
@@ -311,7 +311,7 @@ func (s *Store) ListActiveLimits(ctx context.Context, userID uuid.UUID) ([]Activ
r := table.LimitRecords
stmt := postgres.SELECT(
r.LimitCode, a.Value, r.ReasonCode,
r.ActorType, r.ActorID,
r.ActorType, r.ActorUserID, r.ActorUsername,
r.AppliedAt, r.ExpiresAt,
).
FROM(a.INNER_JOIN(r, r.RecordID.EQ(a.RecordID))).
@@ -331,7 +331,7 @@ func (s *Store) ListActiveLimits(ctx context.Context, userID uuid.UUID) ([]Activ
LimitCode: row.LimitRecords.LimitCode,
Value: row.LimitActive.Value,
ReasonCode: row.LimitRecords.ReasonCode,
Actor: ActorRef{Type: row.LimitRecords.ActorType, ID: derefString(row.LimitRecords.ActorID)},
Actor: actorFromColumns(row.LimitRecords.ActorType, row.LimitRecords.ActorUserID, row.LimitRecords.ActorUsername),
AppliedAt: row.LimitRecords.AppliedAt,
}
if row.LimitRecords.ExpiresAt != nil {
@@ -395,9 +395,12 @@ func (s *Store) ApplyEntitlementTx(ctx context.Context, snap EntitlementSnapshot
if err := s.assertAccountLive(ctx, snap.UserID); err != nil {
return EntitlementSnapshot{}, err
}
err := withTx(ctx, s.db, func(tx *sql.Tx) error {
actorUserID, actorUsername, err := actorToColumnArgs(snap.Actor)
if err != nil {
return EntitlementSnapshot{}, err
}
err = withTx(ctx, s.db, func(tx *sql.Tx) error {
recordID := uuid.New()
actorID := nullableString(snap.Actor.ID)
var endsAt any
if snap.EndsAt != nil {
endsAt = *snap.EndsAt
@@ -409,20 +412,21 @@ func (s *Store) ApplyEntitlementTx(ctx context.Context, snap EntitlementSnapshot
table.EntitlementRecords.IsPaid,
table.EntitlementRecords.Source,
table.EntitlementRecords.ActorType,
table.EntitlementRecords.ActorID,
table.EntitlementRecords.ActorUserID,
table.EntitlementRecords.ActorUsername,
table.EntitlementRecords.ReasonCode,
table.EntitlementRecords.StartsAt,
table.EntitlementRecords.EndsAt,
table.EntitlementRecords.CreatedAt,
).VALUES(
recordID, snap.UserID, snap.Tier, snap.IsPaid, snap.Source,
snap.Actor.Type, actorID, snap.ReasonCode,
snap.Actor.Type, actorUserID, actorUsername, snap.ReasonCode,
snap.StartsAt, endsAt, snap.UpdatedAt,
)
if _, err := recordStmt.ExecContext(ctx, tx); err != nil {
return fmt.Errorf("insert entitlement record: %w", err)
}
return upsertSnapshotTx(ctx, tx, snap)
return upsertSnapshotTx(ctx, tx, snap, actorUserID, actorUsername)
})
if err != nil {
return EntitlementSnapshot{}, err
@@ -437,9 +441,12 @@ func (s *Store) ApplySanctionTx(ctx context.Context, input sanctionInsert) error
if err := s.assertAccountLive(ctx, input.UserID); err != nil {
return err
}
actorUserID, actorUsername, err := actorToColumnArgs(input.Actor)
if err != nil {
return err
}
return withTx(ctx, s.db, func(tx *sql.Tx) error {
recordID := uuid.New()
actorID := nullableString(input.ActorID)
var expiresAt any
if input.ExpiresAt != nil {
expiresAt = *input.ExpiresAt
@@ -451,12 +458,13 @@ func (s *Store) ApplySanctionTx(ctx context.Context, input sanctionInsert) error
table.SanctionRecords.Scope,
table.SanctionRecords.ReasonCode,
table.SanctionRecords.ActorType,
table.SanctionRecords.ActorID,
table.SanctionRecords.ActorUserID,
table.SanctionRecords.ActorUsername,
table.SanctionRecords.AppliedAt,
table.SanctionRecords.ExpiresAt,
).VALUES(
recordID, input.UserID, input.SanctionCode, input.Scope, input.ReasonCode,
input.ActorType, actorID, input.AppliedAt, expiresAt,
input.Actor.Type, actorUserID, actorUsername, input.AppliedAt, expiresAt,
)
if _, err := recordStmt.ExecContext(ctx, tx); err != nil {
return fmt.Errorf("insert sanction record: %w", err)
@@ -498,9 +506,12 @@ func (s *Store) ApplyLimitTx(ctx context.Context, input limitInsert) error {
if err := s.assertAccountLive(ctx, input.UserID); err != nil {
return err
}
actorUserID, actorUsername, err := actorToColumnArgs(input.Actor)
if err != nil {
return err
}
return withTx(ctx, s.db, func(tx *sql.Tx) error {
recordID := uuid.New()
actorID := nullableString(input.ActorID)
var expiresAt any
if input.ExpiresAt != nil {
expiresAt = *input.ExpiresAt
@@ -512,12 +523,13 @@ func (s *Store) ApplyLimitTx(ctx context.Context, input limitInsert) error {
table.LimitRecords.Value,
table.LimitRecords.ReasonCode,
table.LimitRecords.ActorType,
table.LimitRecords.ActorID,
table.LimitRecords.ActorUserID,
table.LimitRecords.ActorUsername,
table.LimitRecords.AppliedAt,
table.LimitRecords.ExpiresAt,
).VALUES(
recordID, input.UserID, input.LimitCode, input.Value, input.ReasonCode,
input.ActorType, actorID, input.AppliedAt, expiresAt,
input.Actor.Type, actorUserID, actorUsername, input.AppliedAt, expiresAt,
)
if _, err := recordStmt.ExecContext(ctx, tx); err != nil {
return fmt.Errorf("insert limit record: %w", err)
@@ -547,12 +559,16 @@ func (s *Store) ApplyLimitTx(ctx context.Context, input limitInsert) error {
// successful idempotent operation.
func (s *Store) SoftDeleteAccount(ctx context.Context, userID uuid.UUID, actor ActorRef, now time.Time) (bool, error) {
a := table.Accounts
actorIDExpr := nullableStringExpr(actor.ID)
actorUserIDExpr, actorUsernameExpr, err := actorToColumnExprs(actor)
if err != nil {
return false, err
}
stmt := a.UPDATE().
SET(
a.DeletedAt.SET(postgres.TimestampzT(now)),
a.DeletedActorType.SET(postgres.String(actor.Type)),
a.DeletedActorID.SET(actorIDExpr),
a.DeletedActorUserID.SET(actorUserIDExpr),
a.DeletedActorUsername.SET(actorUsernameExpr),
a.UpdatedAt.SET(postgres.TimestampzT(now)),
).
WHERE(
@@ -593,18 +609,23 @@ func (s *Store) assertAccountLive(ctx context.Context, userID uuid.UUID) error {
}
func insertSnapshotTx(ctx context.Context, tx *sql.Tx, snap EntitlementSnapshot) error {
actorUserID, actorUsername, err := actorToColumnArgs(snap.Actor)
if err != nil {
return err
}
es := table.EntitlementSnapshots
actorID := nullableString(snap.Actor.ID)
var endsAt any
if snap.EndsAt != nil {
endsAt = *snap.EndsAt
}
stmt := es.INSERT(
es.UserID, es.Tier, es.IsPaid, es.Source, es.ActorType, es.ActorID,
es.UserID, es.Tier, es.IsPaid, es.Source,
es.ActorType, es.ActorUserID, es.ActorUsername,
es.ReasonCode, es.StartsAt, es.EndsAt,
es.MaxRegisteredRaceNames, es.UpdatedAt,
).VALUES(
snap.UserID, snap.Tier, snap.IsPaid, snap.Source, snap.Actor.Type, actorID,
snap.UserID, snap.Tier, snap.IsPaid, snap.Source,
snap.Actor.Type, actorUserID, actorUsername,
snap.ReasonCode, snap.StartsAt, endsAt, snap.MaxRegisteredRaceNames, snap.UpdatedAt,
)
if _, err := stmt.ExecContext(ctx, tx); err != nil {
@@ -613,19 +634,20 @@ func insertSnapshotTx(ctx context.Context, tx *sql.Tx, snap EntitlementSnapshot)
return nil
}
func upsertSnapshotTx(ctx context.Context, tx *sql.Tx, snap EntitlementSnapshot) error {
func upsertSnapshotTx(ctx context.Context, tx *sql.Tx, snap EntitlementSnapshot, actorUserID, actorUsername any) error {
es := table.EntitlementSnapshots
actorID := nullableString(snap.Actor.ID)
var endsAt any
if snap.EndsAt != nil {
endsAt = *snap.EndsAt
}
stmt := es.INSERT(
es.UserID, es.Tier, es.IsPaid, es.Source, es.ActorType, es.ActorID,
es.UserID, es.Tier, es.IsPaid, es.Source,
es.ActorType, es.ActorUserID, es.ActorUsername,
es.ReasonCode, es.StartsAt, es.EndsAt,
es.MaxRegisteredRaceNames, es.UpdatedAt,
).VALUES(
snap.UserID, snap.Tier, snap.IsPaid, snap.Source, snap.Actor.Type, actorID,
snap.UserID, snap.Tier, snap.IsPaid, snap.Source,
snap.Actor.Type, actorUserID, actorUsername,
snap.ReasonCode, snap.StartsAt, endsAt, snap.MaxRegisteredRaceNames, snap.UpdatedAt,
).
ON_CONFLICT(es.UserID).
@@ -634,7 +656,8 @@ func upsertSnapshotTx(ctx context.Context, tx *sql.Tx, snap EntitlementSnapshot)
es.IsPaid.SET(es.EXCLUDED.IsPaid),
es.Source.SET(es.EXCLUDED.Source),
es.ActorType.SET(es.EXCLUDED.ActorType),
es.ActorID.SET(es.EXCLUDED.ActorID),
es.ActorUserID.SET(es.EXCLUDED.ActorUserID),
es.ActorUsername.SET(es.EXCLUDED.ActorUsername),
es.ReasonCode.SET(es.EXCLUDED.ReasonCode),
es.StartsAt.SET(es.EXCLUDED.StartsAt),
es.EndsAt.SET(es.EXCLUDED.EndsAt),
@@ -680,7 +703,7 @@ func modelToSnapshot(row model.EntitlementSnapshots) EntitlementSnapshot {
Tier: row.Tier,
IsPaid: row.IsPaid,
Source: row.Source,
Actor: ActorRef{Type: row.ActorType, ID: derefString(row.ActorID)},
Actor: actorFromColumns(row.ActorType, row.ActorUserID, row.ActorUsername),
ReasonCode: row.ReasonCode,
StartsAt: row.StartsAt,
MaxRegisteredRaceNames: row.MaxRegisteredRaceNames,
@@ -693,31 +716,67 @@ func modelToSnapshot(row model.EntitlementSnapshots) EntitlementSnapshot {
return out
}
// nullableString converts a Go string to the `any` form expected by jet
// VALUES: an empty string becomes nil so the column receives NULL.
func nullableString(v string) any {
if v == "" {
return nil
// actorToColumnArgs converts an ActorRef into the (actor_user_id,
// actor_username) values for jet INSERT VALUES. A nil-typed `any` lands
// as SQL NULL through the database/sql driver. Type=="user" parses ID
// as a UUID; Type=="admin" stores ID verbatim as the username;
// everything else (system, unknown) writes both columns as NULL. An
// empty ID is allowed for "user" so synthetic system events that label
// themselves as "user" do not fail.
func actorToColumnArgs(actor ActorRef) (any, any, error) {
switch strings.TrimSpace(actor.Type) {
case "user":
id := strings.TrimSpace(actor.ID)
if id == "" {
return nil, nil, nil
}
uid, err := uuid.Parse(id)
if err != nil {
return nil, nil, fmt.Errorf("user store: actor id %q is not a uuid: %w", actor.ID, err)
}
return uid, nil, nil
case "admin":
if strings.TrimSpace(actor.ID) == "" {
return nil, nil, nil
}
return nil, actor.ID, nil
default:
return nil, nil, nil
}
return v
}
// nullableStringExpr returns a typed jet expression: the empty string
// produces NULL, otherwise a String literal. Used by UPDATE SET paths
// where jet's SET wants a typed Expression rather than `any`.
func nullableStringExpr(v string) postgres.StringExpression {
if v == "" {
return postgres.StringExp(postgres.NULL)
// actorToColumnExprs is the typed-expression analogue of
// actorToColumnArgs for the UPDATE SET sites. jet's generated bindings
// type uuid columns as ColumnString (the dialect emits an explicit
// CAST), so both returned expressions are StringExpression.
func actorToColumnExprs(actor ActorRef) (postgres.StringExpression, postgres.StringExpression, error) {
uidArg, nameArg, err := actorToColumnArgs(actor)
if err != nil {
return nil, nil, err
}
return postgres.String(v)
uidExpr := postgres.StringExp(postgres.NULL)
if uid, ok := uidArg.(uuid.UUID); ok {
uidExpr = postgres.UUID(uid)
}
nameExpr := postgres.StringExp(postgres.NULL)
if name, ok := nameArg.(string); ok {
nameExpr = postgres.String(name)
}
return uidExpr, nameExpr, nil
}
// derefString returns the empty string when p is nil, otherwise *p.
func derefString(p *string) string {
if p == nil {
return ""
// actorFromColumns reconstructs an ActorRef from the (actor_type,
// actor_user_id, actor_username) triple read from an audit row. The
// non-nil column wins; both nil yields an empty ID.
func actorFromColumns(actorType string, userID *uuid.UUID, username *string) ActorRef {
out := ActorRef{Type: actorType}
switch {
case userID != nil:
out.ID = userID.String()
case username != nil:
out.ID = *username
}
return *p
return out
}
// rowsAffectedOrNotFound returns ErrAccountNotFound when the UPDATE