// Package mictime runs room mic outbox consumers and exposes mic duration queries. package mictime import ( "context" "time" "hyapp/pkg/xerr" mictimedomain "hyapp/services/user-service/internal/domain/mictime" ) // Repository applies room mic events and returns user duration aggregates. type Repository interface { ProcessRoomMicEvent(ctx context.Context, event mictimedomain.RoomMicEvent) (mictimedomain.ApplyResult, error) CompensateOpenSessions(ctx context.Context, options mictimedomain.CompensationOptions) (mictimedomain.CompensationResult, error) GetLifetimeStats(ctx context.Context, appCode string, userID int64) (mictimedomain.LifetimeStats, error) } // Service owns the user mic duration read model. type Service struct { repository Repository } // CompensationWorkerOptions controls abnormal open session closure. type CompensationWorkerOptions struct { WorkerID string BatchSize int PendingPublishMaxAge time.Duration PublishingSessionMaxAge time.Duration } // New creates a mic time service with the repository that owns idempotent application. func New(repository Repository) *Service { return &Service{repository: repository} } // GetLifetimeStats returns user-wide accumulated mic duration. func (s *Service) GetLifetimeStats(ctx context.Context, appCode string, userID int64) (mictimedomain.LifetimeStats, error) { if s == nil || s.repository == nil { return mictimedomain.LifetimeStats{}, xerr.New(xerr.Unavailable, "mic time repository is not configured") } return s.repository.GetLifetimeStats(ctx, appCode, userID) } // ConsumeRoomMicEvent applies one MQ-delivered RoomMicChanged fact. func (s *Service) ConsumeRoomMicEvent(ctx context.Context, event mictimedomain.RoomMicEvent) (mictimedomain.ApplyResult, error) { if s == nil || s.repository == nil { return mictimedomain.ApplyResult{}, xerr.New(xerr.Unavailable, "mic time repository is not configured") } if event.EventType != mictimedomain.RoomMicChangedEventType { return mictimedomain.ApplyResult{Skipped: true, Reason: "unsupported_event_type"}, nil } return s.repository.ProcessRoomMicEvent(ctx, event) } // ProcessOpenSessionCompensationBatch exposes one bounded compensation scan for cron-service. func (s *Service) ProcessOpenSessionCompensationBatch(ctx context.Context, options CompensationWorkerOptions) (mictimedomain.CompensationResult, error) { if s == nil || s.repository == nil { return mictimedomain.CompensationResult{}, xerr.New(xerr.Unavailable, "mic time repository is not configured") } if options.BatchSize <= 0 { options.BatchSize = 100 } if options.PendingPublishMaxAge <= 0 { options.PendingPublishMaxAge = 2 * time.Minute } if options.PublishingSessionMaxAge <= 0 { options.PublishingSessionMaxAge = 12 * time.Hour } return s.repository.CompensateOpenSessions(ctx, mictimedomain.CompensationOptions{ NowMs: time.Now().UnixMilli(), BatchSize: options.BatchSize, PendingPublishMaxAgeMs: options.PendingPublishMaxAge.Milliseconds(), PublishingSessionMaxAgeMs: options.PublishingSessionMaxAge.Milliseconds(), }) }