package domain import ( "context" "time" "github.com/google/uuid" "github.com/lib/pq" "gorm.io/gorm" ) const ( RPUsageOutboxStatusPending = "pending" RPUsageOutboxStatusProcessing = "processing" RPUsageOutboxStatusProcessed = "processed" RPUsageOutboxStatusFailed = "failed" ) const ( RPUsageEventTypeAuthorizationGranted = "rp_usage.authorization_granted" RPUsageEventTypeAuthorizationRevoked = "rp_usage.authorization_revoked" ) const ( RPUsageTenantTypeCompany = TenantTypeCompany RPUsageTenantTypeOrganization = TenantTypeOrganization ) type RPUsageEvent struct { ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()" json:"id"` EventType string `gorm:"not null;index:idx_rp_usage_outbox_event" json:"eventType"` Subject string `gorm:"not null;index:idx_rp_usage_outbox_subject" json:"subject"` TenantID string `gorm:"index:idx_rp_usage_outbox_tenant" json:"tenantId,omitempty"` TenantType string `gorm:"index:idx_rp_usage_outbox_tenant" json:"tenantType,omitempty"` ClientID string `gorm:"not null;index:idx_rp_usage_outbox_client" json:"clientId"` ClientName string `json:"clientName,omitempty"` SessionID string `gorm:"index" json:"sessionId,omitempty"` Scopes pq.StringArray `gorm:"type:text[]" json:"scopes,omitempty"` Source string `gorm:"not null;index" json:"source"` CorrelationID string `gorm:"index" json:"correlationId,omitempty"` Payload JSONMap `gorm:"type:jsonb" json:"payload,omitempty"` DedupeKey string `gorm:"uniqueIndex" json:"dedupeKey"` Status string `gorm:"default:'pending';index" json:"status"` RetryCount int `gorm:"default:0" json:"retryCount"` LastError string `json:"lastError,omitempty"` NextAttemptAt *time.Time `json:"nextAttemptAt,omitempty"` OccurredAt time.Time `gorm:"not null;index" json:"occurredAt"` ProcessedAt *time.Time `json:"processedAt,omitempty"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } func (e *RPUsageEvent) TableName() string { return "rp_usage_outbox" } func (e *RPUsageEvent) BeforeCreate(tx *gorm.DB) error { if e.ID == "" { e.ID = uuid.NewString() } if e.Status == "" { e.Status = RPUsageOutboxStatusPending } if e.OccurredAt.IsZero() { e.OccurredAt = time.Now() } if e.Payload == nil { e.Payload = JSONMap{} } return nil } type RPUsageEventSink interface { EmitRPUsageEvent(ctx context.Context, event RPUsageEvent) error } type RPUsageProjectionRepository interface { CreateRPUsageEvent(ctx context.Context, event RPUsageEvent) error } type RPUsageDailyMetric struct { Date string `json:"date"` TenantID string `json:"tenantId"` TenantType string `json:"tenantType"` TenantName string `json:"tenantName,omitempty"` ClientID string `json:"clientId"` ClientName string `json:"clientName"` LoginRequests uint64 `json:"loginRequests"` OtherRequests uint64 `json:"otherRequests"` UniqueSubjects uint64 `json:"uniqueSubjects"` } type RPUsageQuery struct { Days int Period string TenantID string } type RPUsageQueryRepository interface { FindRPUsage(ctx context.Context, query RPUsageQuery) ([]RPUsageDailyMetric, error) }