package service import ( "context" "regexp" "strings" "time" "watch-party-backend/internal/core/episode" ) // Service implements the episode.UseCases port. type Service struct { repo episode.Repository } func NewEpisodeService(r episode.Repository) *Service { return &Service{repo: r} } func (s *Service) ListAll(ctx context.Context) ([]episode.Episode, error) { c, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return s.repo.ListAll(c) } func (s *Service) ListArchive(ctx context.Context) ([]episode.ArchiveEpisode, error) { c, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return s.repo.ListArchive(c) } func (s *Service) GetCurrent(ctx context.Context) (episode.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$`) const defaultStartTime = "22:00:00" func parseHHMMSSToDuration(hhmmss string) (time.Duration, error) { parts := strings.Split(hhmmss, ":") if len(parts) != 3 { return 0, episode.ErrInvalidPlayback } hours, err := time.ParseDuration(parts[0] + "h") if err != nil { return 0, err } mins, err := time.ParseDuration(parts[1] + "m") if err != nil { return 0, err } secs, err := time.ParseDuration(parts[2] + "s") if err != nil { return 0, err } return hours + mins + secs, nil } func (s *Service) SetCurrent(ctx context.Context, id int64, start string) (episode.Episode, error) { if !hhmmss.MatchString(start) { return episode.Episode{}, episode.ErrInvalidStartTime } c, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() if err := s.repo.SetCurrent(c, id, start); err != nil { return episode.Episode{}, err } return s.repo.GetCurrent(c) } func (s *Service) Create(ctx context.Context, in episode.NewShowInput) (episode.Episode, error) { if in.EpNum <= 0 || strings.TrimSpace(in.EpTitle) == "" || strings.TrimSpace(in.SeasonName) == "" { return episode.Episode{}, episode.ErrInvalidShowInput } in.StartTime = strings.TrimSpace(in.StartTime) if in.StartTime == "" { in.StartTime = defaultStartTime } if !hhmmss.MatchString(in.StartTime) { return episode.Episode{}, episode.ErrInvalidStartTime } if !hhmmss.MatchString(in.PlaybackLength) { return episode.Episode{}, episode.ErrInvalidPlayback } dur, err := parseHHMMSSToDuration(in.PlaybackLength) if err != nil || dur <= 0 { return episode.Episode{}, episode.ErrInvalidPlayback } c, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return s.repo.Create(c, in) } func (s *Service) MoveToArchive(ctx context.Context, ids []int64) (episode.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 episode.MoveResult{}, episode.ErrEmptyIDs } c, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() return s.repo.MoveToArchive(c, uniq) } func (s *Service) Delete(ctx context.Context, id int64) error { c, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return s.repo.Delete(c, id) }