Files
galaxy-game/user/internal/ports/policy_store.go
T
2026-04-10 19:05:02 +02:00

189 lines
7.3 KiB
Go

package ports
import (
"context"
"fmt"
"galaxy/user/internal/domain/common"
"galaxy/user/internal/domain/policy"
)
// SanctionStore persists sanction history records and later remove-state
// updates.
type SanctionStore interface {
// Create stores one new sanction history record. Implementations must wrap
// ErrConflict when record.RecordID already exists.
Create(ctx context.Context, record policy.SanctionRecord) error
// GetByRecordID returns the sanction history record identified by recordID.
GetByRecordID(ctx context.Context, recordID policy.SanctionRecordID) (policy.SanctionRecord, error)
// ListByUserID returns every sanction history record owned by userID.
ListByUserID(ctx context.Context, userID common.UserID) ([]policy.SanctionRecord, error)
// Update replaces one stored sanction history record.
Update(ctx context.Context, record policy.SanctionRecord) error
}
// LimitStore persists user-specific limit history records and later
// remove-state updates.
type LimitStore interface {
// Create stores one new limit history record. Implementations must wrap
// ErrConflict when record.RecordID already exists.
Create(ctx context.Context, record policy.LimitRecord) error
// GetByRecordID returns the limit history record identified by recordID.
GetByRecordID(ctx context.Context, recordID policy.LimitRecordID) (policy.LimitRecord, error)
// ListByUserID returns every limit history record owned by userID.
ListByUserID(ctx context.Context, userID common.UserID) ([]policy.LimitRecord, error)
// Update replaces one stored limit history record.
Update(ctx context.Context, record policy.LimitRecord) error
}
// ApplySanctionInput stores one atomic creation of a new active sanction.
type ApplySanctionInput struct {
// NewRecord stores the sanction history record that must become active.
NewRecord policy.SanctionRecord
}
// Validate reports whether ApplySanctionInput is structurally complete.
func (input ApplySanctionInput) Validate() error {
if err := input.NewRecord.Validate(); err != nil {
return fmt.Errorf("apply sanction input new record: %w", err)
}
return nil
}
// RemoveSanctionInput stores one atomic removal of the current active
// sanction for one `user_id + sanction_code`.
type RemoveSanctionInput struct {
// ExpectedActiveRecord stores the exact sanction record that must still be
// active before the mutation commits.
ExpectedActiveRecord policy.SanctionRecord
// UpdatedRecord stores ExpectedActiveRecord after remove metadata is
// applied.
UpdatedRecord policy.SanctionRecord
}
// Validate reports whether RemoveSanctionInput is structurally complete.
func (input RemoveSanctionInput) Validate() error {
if err := input.ExpectedActiveRecord.Validate(); err != nil {
return fmt.Errorf("remove sanction input expected active record: %w", err)
}
if err := input.UpdatedRecord.Validate(); err != nil {
return fmt.Errorf("remove sanction input updated record: %w", err)
}
if input.ExpectedActiveRecord.RecordID != input.UpdatedRecord.RecordID {
return fmt.Errorf("remove sanction input updated record must preserve record id")
}
if input.ExpectedActiveRecord.UserID != input.UpdatedRecord.UserID {
return fmt.Errorf("remove sanction input records must belong to the same user id")
}
if input.ExpectedActiveRecord.SanctionCode != input.UpdatedRecord.SanctionCode {
return fmt.Errorf("remove sanction input records must preserve sanction code")
}
return nil
}
// SetLimitInput stores one atomic creation or replacement of the current
// active limit for one `user_id + limit_code`.
type SetLimitInput struct {
// ExpectedActiveRecord stores the currently active limit that must still be
// active before replacement commits. It stays nil when no active limit
// exists yet.
ExpectedActiveRecord *policy.LimitRecord
// UpdatedActiveRecord stores ExpectedActiveRecord after remove metadata is
// applied. It stays nil when no active limit exists yet.
UpdatedActiveRecord *policy.LimitRecord
// NewRecord stores the limit history record that must become active.
NewRecord policy.LimitRecord
}
// Validate reports whether SetLimitInput is structurally complete.
func (input SetLimitInput) Validate() error {
if err := input.NewRecord.Validate(); err != nil {
return fmt.Errorf("set limit input new record: %w", err)
}
switch {
case input.ExpectedActiveRecord == nil && input.UpdatedActiveRecord == nil:
return nil
case input.ExpectedActiveRecord == nil || input.UpdatedActiveRecord == nil:
return fmt.Errorf("set limit input active replacement records must both be present or absent")
}
if err := input.ExpectedActiveRecord.Validate(); err != nil {
return fmt.Errorf("set limit input expected active record: %w", err)
}
if err := input.UpdatedActiveRecord.Validate(); err != nil {
return fmt.Errorf("set limit input updated active record: %w", err)
}
if input.ExpectedActiveRecord.RecordID != input.UpdatedActiveRecord.RecordID {
return fmt.Errorf("set limit input updated active record must preserve record id")
}
if input.ExpectedActiveRecord.UserID != input.UpdatedActiveRecord.UserID ||
input.ExpectedActiveRecord.UserID != input.NewRecord.UserID {
return fmt.Errorf("set limit input records must belong to the same user id")
}
if input.ExpectedActiveRecord.LimitCode != input.UpdatedActiveRecord.LimitCode ||
input.ExpectedActiveRecord.LimitCode != input.NewRecord.LimitCode {
return fmt.Errorf("set limit input records must preserve limit code")
}
return nil
}
// RemoveLimitInput stores one atomic removal of the current active limit for
// one `user_id + limit_code`.
type RemoveLimitInput struct {
// ExpectedActiveRecord stores the exact limit record that must still be
// active before the mutation commits.
ExpectedActiveRecord policy.LimitRecord
// UpdatedRecord stores ExpectedActiveRecord after remove metadata is
// applied.
UpdatedRecord policy.LimitRecord
}
// Validate reports whether RemoveLimitInput is structurally complete.
func (input RemoveLimitInput) Validate() error {
if err := input.ExpectedActiveRecord.Validate(); err != nil {
return fmt.Errorf("remove limit input expected active record: %w", err)
}
if err := input.UpdatedRecord.Validate(); err != nil {
return fmt.Errorf("remove limit input updated record: %w", err)
}
if input.ExpectedActiveRecord.RecordID != input.UpdatedRecord.RecordID {
return fmt.Errorf("remove limit input updated record must preserve record id")
}
if input.ExpectedActiveRecord.UserID != input.UpdatedRecord.UserID {
return fmt.Errorf("remove limit input records must belong to the same user id")
}
if input.ExpectedActiveRecord.LimitCode != input.UpdatedRecord.LimitCode {
return fmt.Errorf("remove limit input records must preserve limit code")
}
return nil
}
// PolicyLifecycleStore persists atomic sanction and limit transitions that
// must keep history and active-slot state consistent.
type PolicyLifecycleStore interface {
// ApplySanction atomically creates one new active sanction record.
ApplySanction(ctx context.Context, input ApplySanctionInput) error
// RemoveSanction atomically removes one active sanction record.
RemoveSanction(ctx context.Context, input RemoveSanctionInput) error
// SetLimit atomically creates or replaces one active limit record.
SetLimit(ctx context.Context, input SetLimitInput) error
// RemoveLimit atomically removes one active limit record.
RemoveLimit(ctx context.Context, input RemoveLimitInput) error
}