package service import ( "context" "errors" "regexp" "time" "watch-party-backend/internal/repo" ) var ( ErrInvalidTime = errors.New("invalid start_time (expected HH:MM:SS)") ErrEmptyIDs = errors.New("ids must not be empty") ) type EpisodeService interface { GetCurrent(ctx context.Context) (repo.Episode, error) SetCurrent(ctx context.Context, id int64, start string) (repo.Episode, error) MoveToArchive(ctx context.Context, ids []int64) (repo.MoveResult, error) ListAll(ctx context.Context) ([]repo.Episode, error) } type episodeService struct { repo repo.EpisodeRepository } func NewEpisodeService(r repo.EpisodeRepository) EpisodeService { return &episodeService{repo: r} } func (s *episodeService) ListAll(ctx context.Context) ([]repo.Episode, error) { c, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return s.repo.ListAll(c) } func (s *episodeService) GetCurrent(ctx context.Context) (repo.Episode, error) { c, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() return s.repo.GetCurrent(c) } var hhmmss = regexp.MustCompile(`^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$`) func (s *episodeService) SetCurrent(ctx context.Context, id int64, start string) (repo.Episode, error) { if !hhmmss.MatchString(start) { return repo.Episode{}, ErrInvalidTime } c, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() if err := s.repo.SetCurrent(c, id, start); err != nil { return repo.Episode{}, err } return s.repo.GetCurrent(c) } func (s *episodeService) MoveToArchive(ctx context.Context, ids []int64) (repo.MoveResult, error) { uniq := make([]int64, 0, len(ids)) seen := make(map[int64]struct{}, len(ids)) for _, id := range ids { if _, ok := seen[id]; !ok { seen[id] = struct{}{} uniq = append(uniq, id) } } if len(uniq) == 0 { return repo.MoveResult{}, ErrEmptyIDs } c, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() return s.repo.MoveToArchive(c, uniq) }