package examprep import ( "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/ports" "context" "errors" "github.com/jackc/pgx/v5" ) var ErrCatalogCourseNotFound = errors.New("exam prep catalog course not found") var ErrUnitNotFound = errors.New("exam prep unit not found") var ErrModuleNotFound = errors.New("exam prep module not found") var ErrLessonNotFound = errors.New("exam prep lesson not found") var ErrPracticeNotFound = errors.New("exam prep practice not found") // examPrepStore is implemented by *repository.Store (catalog courses, units, modules, lessons, practices). type examPrepStore interface { ports.ExamPrepCatalogCourseStore ports.ExamPrepUnitStore ports.ExamPrepModuleStore ports.ExamPrepLessonStore ports.ExamPrepPracticeStore } type Service struct { store examPrepStore } func NewService(store examPrepStore) *Service { return &Service{store: store} } func (s *Service) CreateCatalogCourse(ctx context.Context, input domain.CreateExamPrepCatalogCourseInput) (domain.ExamPrepCatalogCourse, error) { return s.store.CreateExamPrepCatalogCourse(ctx, input) } func (s *Service) GetCatalogCourseByID(ctx context.Context, id int64) (domain.ExamPrepCatalogCourse, error) { c, err := s.store.GetExamPrepCatalogCourseByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepCatalogCourse{}, ErrCatalogCourseNotFound } return domain.ExamPrepCatalogCourse{}, err } return c, nil } func (s *Service) ListCatalogCourses(ctx context.Context, limit, offset int32) ([]domain.ExamPrepCatalogCourse, int64, error) { if limit <= 0 { limit = 20 } if limit > 200 { limit = 200 } if offset < 0 { offset = 0 } return s.store.ListExamPrepCatalogCourses(ctx, limit, offset) } func (s *Service) UpdateCatalogCourse(ctx context.Context, id int64, input domain.UpdateExamPrepCatalogCourseInput) (domain.ExamPrepCatalogCourse, error) { c, err := s.store.UpdateExamPrepCatalogCourse(ctx, id, input) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepCatalogCourse{}, ErrCatalogCourseNotFound } return domain.ExamPrepCatalogCourse{}, err } return c, nil } func (s *Service) DeleteCatalogCourse(ctx context.Context, id int64) error { if _, err := s.store.GetExamPrepCatalogCourseByID(ctx, id); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrCatalogCourseNotFound } return err } return s.store.DeleteExamPrepCatalogCourse(ctx, id) } func (s *Service) ReorderCatalogCourses(ctx context.Context, ordered []int64) error { expected, err := s.store.ListAllExamPrepCatalogCourseIDs(ctx) if err != nil { return err } if err := domain.ValidateReorderPermutation(ordered, expected); err != nil { return err } if len(ordered) == 0 { return nil } return s.store.ReorderExamPrepCatalogCourses(ctx, ordered) } func (s *Service) ensureCatalogCourse(ctx context.Context, catalogCourseID int64) error { if _, err := s.store.GetExamPrepCatalogCourseByID(ctx, catalogCourseID); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrCatalogCourseNotFound } return err } return nil } func (s *Service) CreateUnit(ctx context.Context, catalogCourseID int64, input domain.CreateExamPrepUnitInput) (domain.ExamPrepUnit, error) { if err := s.ensureCatalogCourse(ctx, catalogCourseID); err != nil { return domain.ExamPrepUnit{}, err } return s.store.CreateExamPrepUnit(ctx, catalogCourseID, input) } func (s *Service) ListUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, limit, offset int32) ([]domain.ExamPrepUnit, int64, error) { if err := s.ensureCatalogCourse(ctx, catalogCourseID); err != nil { return nil, 0, err } if limit <= 0 { limit = 20 } if limit > 200 { limit = 200 } if offset < 0 { offset = 0 } return s.store.ListExamPrepUnitsByCatalogCourse(ctx, catalogCourseID, limit, offset) } func (s *Service) GetUnitByID(ctx context.Context, id int64) (domain.ExamPrepUnit, error) { u, err := s.store.GetExamPrepUnitByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepUnit{}, ErrUnitNotFound } return domain.ExamPrepUnit{}, err } return u, nil } func (s *Service) UpdateUnit(ctx context.Context, id int64, input domain.UpdateExamPrepUnitInput) (domain.ExamPrepUnit, error) { u, err := s.store.UpdateExamPrepUnit(ctx, id, input) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepUnit{}, ErrUnitNotFound } return domain.ExamPrepUnit{}, err } return u, nil } func (s *Service) DeleteUnit(ctx context.Context, id int64) error { if _, err := s.store.GetExamPrepUnitByID(ctx, id); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrUnitNotFound } return err } return s.store.DeleteExamPrepUnit(ctx, id) } func (s *Service) ReorderUnitsInCatalogCourse(ctx context.Context, catalogCourseID int64, ordered []int64) error { if err := s.ensureCatalogCourse(ctx, catalogCourseID); err != nil { return err } expected, err := s.store.ListExamPrepUnitIDsByCatalogCourse(ctx, catalogCourseID) if err != nil { return err } if err := domain.ValidateReorderPermutation(ordered, expected); err != nil { return err } if len(ordered) == 0 { return nil } return s.store.ReorderExamPrepUnitsInCatalogCourse(ctx, catalogCourseID, ordered) } func (s *Service) ensureUnit(ctx context.Context, unitID int64) error { if _, err := s.store.GetExamPrepUnitByID(ctx, unitID); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrUnitNotFound } return err } return nil } func (s *Service) CreateModule(ctx context.Context, unitID int64, input domain.CreateExamPrepModuleInput) (domain.ExamPrepModule, error) { if err := s.ensureUnit(ctx, unitID); err != nil { return domain.ExamPrepModule{}, err } return s.store.CreateExamPrepUnitModule(ctx, unitID, input) } func (s *Service) ListModulesByUnit(ctx context.Context, unitID int64, limit, offset int32) ([]domain.ExamPrepModule, int64, error) { if err := s.ensureUnit(ctx, unitID); err != nil { return nil, 0, err } if limit <= 0 { limit = 20 } if limit > 200 { limit = 200 } if offset < 0 { offset = 0 } return s.store.ListExamPrepUnitModulesByUnit(ctx, unitID, limit, offset) } func (s *Service) GetModuleByID(ctx context.Context, id int64) (domain.ExamPrepModule, error) { m, err := s.store.GetExamPrepUnitModuleByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepModule{}, ErrModuleNotFound } return domain.ExamPrepModule{}, err } return m, nil } func (s *Service) UpdateModule(ctx context.Context, id int64, input domain.UpdateExamPrepModuleInput) (domain.ExamPrepModule, error) { m, err := s.store.UpdateExamPrepUnitModule(ctx, id, input) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepModule{}, ErrModuleNotFound } return domain.ExamPrepModule{}, err } return m, nil } func (s *Service) DeleteModule(ctx context.Context, id int64) error { if _, err := s.store.GetExamPrepUnitModuleByID(ctx, id); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrModuleNotFound } return err } return s.store.DeleteExamPrepUnitModule(ctx, id) } func (s *Service) ReorderModulesInUnit(ctx context.Context, unitID int64, ordered []int64) error { if err := s.ensureUnit(ctx, unitID); err != nil { return err } expected, err := s.store.ListExamPrepUnitModuleIDsByUnit(ctx, unitID) if err != nil { return err } if err := domain.ValidateReorderPermutation(ordered, expected); err != nil { return err } if len(ordered) == 0 { return nil } return s.store.ReorderExamPrepUnitModulesInUnit(ctx, unitID, ordered) } func (s *Service) ensureModule(ctx context.Context, unitModuleID int64) error { if _, err := s.store.GetExamPrepUnitModuleByID(ctx, unitModuleID); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrModuleNotFound } return err } return nil } func (s *Service) CreateLesson(ctx context.Context, unitModuleID int64, input domain.CreateExamPrepLessonInput) (domain.ExamPrepLesson, error) { if err := s.ensureModule(ctx, unitModuleID); err != nil { return domain.ExamPrepLesson{}, err } return s.store.CreateExamPrepUnitModuleLesson(ctx, unitModuleID, input) } func (s *Service) ListLessonsByUnitModule(ctx context.Context, unitModuleID int64, limit, offset int32) ([]domain.ExamPrepLesson, int64, error) { if err := s.ensureModule(ctx, unitModuleID); err != nil { return nil, 0, err } if limit <= 0 { limit = 20 } if limit > 200 { limit = 200 } if offset < 0 { offset = 0 } return s.store.ListExamPrepUnitModuleLessonsByUnitModuleID(ctx, unitModuleID, limit, offset) } func (s *Service) GetLessonByID(ctx context.Context, id int64) (domain.ExamPrepLesson, error) { l, err := s.store.GetExamPrepUnitModuleLessonByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepLesson{}, ErrLessonNotFound } return domain.ExamPrepLesson{}, err } return l, nil } func (s *Service) UpdateLesson(ctx context.Context, id int64, input domain.UpdateExamPrepLessonInput) (domain.ExamPrepLesson, error) { l, err := s.store.UpdateExamPrepUnitModuleLesson(ctx, id, input) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepLesson{}, ErrLessonNotFound } return domain.ExamPrepLesson{}, err } return l, nil } func (s *Service) DeleteLesson(ctx context.Context, id int64) error { if _, err := s.store.GetExamPrepUnitModuleLessonByID(ctx, id); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrLessonNotFound } return err } return s.store.DeleteExamPrepUnitModuleLesson(ctx, id) } func (s *Service) ReorderLessonsInUnitModule(ctx context.Context, unitModuleID int64, ordered []int64) error { if err := s.ensureModule(ctx, unitModuleID); err != nil { return err } expected, err := s.store.ListExamPrepUnitModuleLessonIDsByUnitModule(ctx, unitModuleID) if err != nil { return err } if err := domain.ValidateReorderPermutation(ordered, expected); err != nil { return err } if len(ordered) == 0 { return nil } return s.store.ReorderExamPrepUnitModuleLessonsInUnitModule(ctx, unitModuleID, ordered) } func (s *Service) ensureLesson(ctx context.Context, lessonID int64) error { if _, err := s.store.GetExamPrepUnitModuleLessonByID(ctx, lessonID); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrLessonNotFound } return err } return nil } func (s *Service) CreateExamPrepPractice(ctx context.Context, lessonID int64, input domain.CreateExamPrepPracticeInput) (domain.ExamPrepPractice, error) { if err := s.ensureLesson(ctx, lessonID); err != nil { return domain.ExamPrepPractice{}, err } return s.store.CreateExamPrepLessonPractice(ctx, lessonID, input) } func (s *Service) ListExamPrepPracticesByLesson(ctx context.Context, lessonID int64, limit, offset int32) ([]domain.ExamPrepPractice, int64, error) { if err := s.ensureLesson(ctx, lessonID); err != nil { return nil, 0, err } if limit <= 0 { limit = 20 } if limit > 200 { limit = 200 } if offset < 0 { offset = 0 } return s.store.ListExamPrepLessonPracticesByLessonID(ctx, lessonID, limit, offset) } func (s *Service) GetExamPrepPracticeByID(ctx context.Context, id int64) (domain.ExamPrepPractice, error) { p, err := s.store.GetExamPrepLessonPracticeByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepPractice{}, ErrPracticeNotFound } return domain.ExamPrepPractice{}, err } return p, nil } func (s *Service) UpdateExamPrepPractice(ctx context.Context, id int64, input domain.UpdateExamPrepPracticeInput) (domain.ExamPrepPractice, error) { p, err := s.store.UpdateExamPrepLessonPractice(ctx, id, input) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.ExamPrepPractice{}, ErrPracticeNotFound } return domain.ExamPrepPractice{}, err } return p, nil } func (s *Service) DeleteExamPrepPractice(ctx context.Context, id int64) error { if _, err := s.store.GetExamPrepLessonPracticeByID(ctx, id); err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrPracticeNotFound } return err } return s.store.DeleteExamPrepLessonPractice(ctx, id) }