Yimaru-BackEnd/internal/services/lessons/service.go
Yared Yemane dfdb2a52dc feat: add LMS lesson reorder endpoint within a module
Add PUT /api/v1/modules/:moduleId/lessons/reorder with lessons.reorder permission, matching the existing modules and exam-prep lesson reorder patterns.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 03:28:13 -07:00

108 lines
2.8 KiB
Go

package lessons
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/ports"
"Yimaru-Backend/internal/services/modules"
"context"
"errors"
"github.com/jackc/pgx/v5"
)
var ErrLessonNotFound = errors.New("lesson not found")
type Service struct {
lessons ports.LessonStore
modules ports.ModuleStore
}
func NewService(lessons ports.LessonStore, modules ports.ModuleStore) *Service {
return &Service{lessons: lessons, modules: modules}
}
func (s *Service) getModuleOrErr(ctx context.Context, moduleID int64) error {
_, err := s.modules.GetModuleByID(ctx, moduleID)
if err == nil {
return nil
}
if errors.Is(err, pgx.ErrNoRows) {
return modules.ErrModuleNotFound
}
return err
}
func (s *Service) Create(ctx context.Context, moduleID int64, input domain.CreateLessonInput) (domain.Lesson, error) {
if err := s.getModuleOrErr(ctx, moduleID); err != nil {
return domain.Lesson{}, err
}
return s.lessons.CreateLesson(ctx, moduleID, input)
}
func (s *Service) GetByID(ctx context.Context, id int64) (domain.Lesson, error) {
l, err := s.lessons.GetLessonByID(ctx, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.Lesson{}, ErrLessonNotFound
}
return domain.Lesson{}, err
}
return l, nil
}
func (s *Service) ListByModule(ctx context.Context, moduleID int64, publishedOnly bool, limit, offset int32) ([]domain.Lesson, int64, error) {
if err := s.getModuleOrErr(ctx, moduleID); err != nil {
return nil, 0, err
}
if limit <= 0 {
limit = 20
}
if limit > 200 {
limit = 200
}
if offset < 0 {
offset = 0
}
return s.lessons.ListLessonsByModuleID(ctx, moduleID, publishedOnly, limit, offset)
}
func (s *Service) Update(ctx context.Context, id int64, input domain.UpdateLessonInput) (domain.Lesson, error) {
l, err := s.lessons.UpdateLesson(ctx, id, input)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.Lesson{}, ErrLessonNotFound
}
return domain.Lesson{}, err
}
return l, nil
}
func (s *Service) Delete(ctx context.Context, id int64) error {
if _, err := s.lessons.GetLessonByID(ctx, id); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrLessonNotFound
}
return err
}
return s.lessons.DeleteLesson(ctx, id)
}
// ReorderInModule sets sort_order for all lessons in the module. ordered must list every lesson id
// exactly once (e.g. from GET /modules/{moduleId}/lessons) in the desired order.
func (s *Service) ReorderInModule(ctx context.Context, moduleID int64, ordered []int64) error {
if err := s.getModuleOrErr(ctx, moduleID); err != nil {
return err
}
expected, err := s.lessons.ListLessonIDsByModule(ctx, moduleID)
if err != nil {
return err
}
if err := domain.ValidateReorderPermutation(ordered, expected); err != nil {
return err
}
if len(ordered) == 0 {
return nil
}
return s.lessons.ReorderLessonsInModule(ctx, moduleID, ordered)
}