feat: user service

This commit is contained in:
Ilia Denisov
2026-04-10 19:05:02 +02:00
committed by GitHub
parent 710bad712e
commit 23ffcb7535
140 changed files with 33418 additions and 952 deletions
+230
View File
@@ -0,0 +1,230 @@
package ports
import (
"context"
"fmt"
"galaxy/user/internal/domain/common"
"galaxy/user/internal/domain/entitlement"
)
// EntitlementHistoryStore persists immutable entitlement period records and
// later close-state updates.
type EntitlementHistoryStore interface {
// Create stores one new entitlement period history record. Implementations
// must wrap ErrConflict when record.RecordID already exists.
Create(ctx context.Context, record entitlement.PeriodRecord) error
// GetByRecordID returns the entitlement period history record identified by
// recordID.
GetByRecordID(ctx context.Context, recordID entitlement.EntitlementRecordID) (entitlement.PeriodRecord, error)
// ListByUserID returns every entitlement period history record owned by
// userID.
ListByUserID(ctx context.Context, userID common.UserID) ([]entitlement.PeriodRecord, error)
// Update replaces one stored entitlement period history record.
Update(ctx context.Context, record entitlement.PeriodRecord) error
}
// EntitlementSnapshotStore persists the read-optimized current entitlement
// snapshot.
type EntitlementSnapshotStore interface {
// GetByUserID returns the current entitlement snapshot for userID.
GetByUserID(ctx context.Context, userID common.UserID) (entitlement.CurrentSnapshot, error)
// Put stores the current entitlement snapshot for record.UserID.
Put(ctx context.Context, record entitlement.CurrentSnapshot) error
}
// GrantEntitlementInput stores one atomic transition from a current free
// entitlement state to a current paid state.
type GrantEntitlementInput struct {
// ExpectedCurrentSnapshot stores the exact snapshot that must still be
// current before the mutation commits.
ExpectedCurrentSnapshot entitlement.CurrentSnapshot
// ExpectedCurrentRecord stores the current effective free period that must
// still be current before the mutation commits.
ExpectedCurrentRecord entitlement.PeriodRecord
// UpdatedCurrentRecord stores ExpectedCurrentRecord after the close metadata
// is applied.
UpdatedCurrentRecord entitlement.PeriodRecord
// NewRecord stores the new paid entitlement history segment.
NewRecord entitlement.PeriodRecord
// NewSnapshot stores the new current effective entitlement snapshot.
NewSnapshot entitlement.CurrentSnapshot
}
// Validate reports whether GrantEntitlementInput is structurally complete.
func (input GrantEntitlementInput) Validate() error {
if err := input.ExpectedCurrentSnapshot.Validate(); err != nil {
return fmt.Errorf("grant entitlement input expected current snapshot: %w", err)
}
if err := input.ExpectedCurrentRecord.Validate(); err != nil {
return fmt.Errorf("grant entitlement input expected current record: %w", err)
}
if err := input.UpdatedCurrentRecord.Validate(); err != nil {
return fmt.Errorf("grant entitlement input updated current record: %w", err)
}
if err := input.NewRecord.Validate(); err != nil {
return fmt.Errorf("grant entitlement input new record: %w", err)
}
if err := input.NewSnapshot.Validate(); err != nil {
return fmt.Errorf("grant entitlement input new snapshot: %w", err)
}
if input.ExpectedCurrentSnapshot.UserID != input.ExpectedCurrentRecord.UserID ||
input.ExpectedCurrentSnapshot.UserID != input.UpdatedCurrentRecord.UserID ||
input.ExpectedCurrentSnapshot.UserID != input.NewRecord.UserID ||
input.ExpectedCurrentSnapshot.UserID != input.NewSnapshot.UserID {
return fmt.Errorf("grant entitlement input all records must belong to the same user id")
}
if input.ExpectedCurrentRecord.RecordID != input.UpdatedCurrentRecord.RecordID {
return fmt.Errorf("grant entitlement input updated current record must preserve record id")
}
return nil
}
// ExtendEntitlementInput stores one atomic extension of a current finite paid
// entitlement state.
type ExtendEntitlementInput struct {
// ExpectedCurrentSnapshot stores the exact snapshot that must still be
// current before the mutation commits.
ExpectedCurrentSnapshot entitlement.CurrentSnapshot
// NewRecord stores the appended entitlement history segment that extends the
// current paid state.
NewRecord entitlement.PeriodRecord
// NewSnapshot stores the replacement current effective entitlement snapshot.
NewSnapshot entitlement.CurrentSnapshot
}
// Validate reports whether ExtendEntitlementInput is structurally complete.
func (input ExtendEntitlementInput) Validate() error {
if err := input.ExpectedCurrentSnapshot.Validate(); err != nil {
return fmt.Errorf("extend entitlement input expected current snapshot: %w", err)
}
if err := input.NewRecord.Validate(); err != nil {
return fmt.Errorf("extend entitlement input new record: %w", err)
}
if err := input.NewSnapshot.Validate(); err != nil {
return fmt.Errorf("extend entitlement input new snapshot: %w", err)
}
if input.ExpectedCurrentSnapshot.UserID != input.NewRecord.UserID ||
input.ExpectedCurrentSnapshot.UserID != input.NewSnapshot.UserID {
return fmt.Errorf("extend entitlement input all records must belong to the same user id")
}
return nil
}
// RevokeEntitlementInput stores one atomic transition from a current paid
// entitlement state to a new free state.
type RevokeEntitlementInput struct {
// ExpectedCurrentSnapshot stores the exact snapshot that must still be
// current before the mutation commits.
ExpectedCurrentSnapshot entitlement.CurrentSnapshot
// ExpectedCurrentRecord stores the current effective paid period that must
// still be current before the mutation commits.
ExpectedCurrentRecord entitlement.PeriodRecord
// UpdatedCurrentRecord stores ExpectedCurrentRecord after the close metadata
// is applied.
UpdatedCurrentRecord entitlement.PeriodRecord
// NewRecord stores the newly created free entitlement period.
NewRecord entitlement.PeriodRecord
// NewSnapshot stores the replacement current effective free snapshot.
NewSnapshot entitlement.CurrentSnapshot
}
// Validate reports whether RevokeEntitlementInput is structurally complete.
func (input RevokeEntitlementInput) Validate() error {
if err := input.ExpectedCurrentSnapshot.Validate(); err != nil {
return fmt.Errorf("revoke entitlement input expected current snapshot: %w", err)
}
if err := input.ExpectedCurrentRecord.Validate(); err != nil {
return fmt.Errorf("revoke entitlement input expected current record: %w", err)
}
if err := input.UpdatedCurrentRecord.Validate(); err != nil {
return fmt.Errorf("revoke entitlement input updated current record: %w", err)
}
if err := input.NewRecord.Validate(); err != nil {
return fmt.Errorf("revoke entitlement input new record: %w", err)
}
if err := input.NewSnapshot.Validate(); err != nil {
return fmt.Errorf("revoke entitlement input new snapshot: %w", err)
}
if input.ExpectedCurrentSnapshot.UserID != input.ExpectedCurrentRecord.UserID ||
input.ExpectedCurrentSnapshot.UserID != input.UpdatedCurrentRecord.UserID ||
input.ExpectedCurrentSnapshot.UserID != input.NewRecord.UserID ||
input.ExpectedCurrentSnapshot.UserID != input.NewSnapshot.UserID {
return fmt.Errorf("revoke entitlement input all records must belong to the same user id")
}
if input.ExpectedCurrentRecord.RecordID != input.UpdatedCurrentRecord.RecordID {
return fmt.Errorf("revoke entitlement input updated current record must preserve record id")
}
return nil
}
// RepairExpiredEntitlementInput stores one atomic lazy-repair transition from
// an expired finite paid snapshot to a materialized free state.
type RepairExpiredEntitlementInput struct {
// ExpectedExpiredSnapshot stores the exact expired snapshot that must still
// be current before the repair commits.
ExpectedExpiredSnapshot entitlement.CurrentSnapshot
// NewRecord stores the newly created free entitlement period.
NewRecord entitlement.PeriodRecord
// NewSnapshot stores the replacement current effective free snapshot.
NewSnapshot entitlement.CurrentSnapshot
}
// Validate reports whether RepairExpiredEntitlementInput is structurally
// complete.
func (input RepairExpiredEntitlementInput) Validate() error {
if err := input.ExpectedExpiredSnapshot.Validate(); err != nil {
return fmt.Errorf("repair expired entitlement input expected expired snapshot: %w", err)
}
if err := input.NewRecord.Validate(); err != nil {
return fmt.Errorf("repair expired entitlement input new record: %w", err)
}
if err := input.NewSnapshot.Validate(); err != nil {
return fmt.Errorf("repair expired entitlement input new snapshot: %w", err)
}
if input.ExpectedExpiredSnapshot.UserID != input.NewRecord.UserID ||
input.ExpectedExpiredSnapshot.UserID != input.NewSnapshot.UserID {
return fmt.Errorf("repair expired entitlement input all records must belong to the same user id")
}
return nil
}
// EntitlementLifecycleStore persists atomic entitlement timeline transitions
// that must keep history and current snapshot consistent.
type EntitlementLifecycleStore interface {
// Grant atomically closes the current free period, creates a new paid
// period, and replaces the current snapshot.
Grant(ctx context.Context, input GrantEntitlementInput) error
// Extend atomically appends one paid-history segment and replaces the
// current snapshot.
Extend(ctx context.Context, input ExtendEntitlementInput) error
// Revoke atomically closes the current paid period, creates a new free
// period, and replaces the current snapshot.
Revoke(ctx context.Context, input RevokeEntitlementInput) error
// RepairExpired atomically replaces one expired finite paid snapshot with a
// materialized free state.
RepairExpired(ctx context.Context, input RepairExpiredEntitlementInput) error
}