package repository import ( "context" "errors" dbgen "Yimaru-Backend/gen/db" "Yimaru-Backend/internal/domain" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) func lessonToDomain(l dbgen.Lesson) domain.Lesson { out := domain.Lesson{ ID: l.ID, ModuleID: l.ModuleID, Title: l.Title, PublishStatus: domain.LessonPublishStatusFromDB(l.PublishStatus), } out.VideoURL = fromPgText(l.VideoUrl) out.Thumbnail = fromPgText(l.Thumbnail) out.Description = fromPgText(l.Description) out.CreatedAt = l.CreatedAt.Time if l.UpdatedAt.Valid { t := l.UpdatedAt.Time out.UpdatedAt = &t } out.SortOrder = int(l.SortOrder) return out } func (s *Store) CreateLesson(ctx context.Context, moduleID int64, input domain.CreateLessonInput) (domain.Lesson, error) { pub := string(domain.LessonPublishStatusFromCreateInput(input.PublishStatus)) if input.SortOrder != nil { q, tx, err := s.BeginTx(ctx) if err != nil { return domain.Lesson{}, err } defer func() { _ = tx.Rollback(ctx) }() target := int32(*input.SortOrder) if _, err := tx.Exec(ctx, `UPDATE lessons SET sort_order = sort_order + 1 WHERE module_id = $1 AND sort_order >= $2`, moduleID, target, ); err != nil { return domain.Lesson{}, err } l, err := q.CreateLesson(ctx, dbgen.CreateLessonParams{ ModuleID: moduleID, Title: input.Title, VideoUrl: toPgText(input.VideoURL), Thumbnail: toPgText(input.Thumbnail), Description: toPgText(input.Description), SortOrder: pgtype.Int4{Int32: target, Valid: true}, PublishStatus: pub, }) if err != nil { return domain.Lesson{}, err } if err := tx.Commit(ctx); err != nil { return domain.Lesson{}, err } return lessonToDomain(l), nil } l, err := s.queries.CreateLesson(ctx, dbgen.CreateLessonParams{ ModuleID: moduleID, Title: input.Title, VideoUrl: toPgText(input.VideoURL), Thumbnail: toPgText(input.Thumbnail), Description: toPgText(input.Description), SortOrder: pgtype.Int4{Valid: false}, PublishStatus: pub, }) if err != nil { return domain.Lesson{}, err } return lessonToDomain(l), nil } func (s *Store) GetLessonByID(ctx context.Context, id int64) (domain.Lesson, error) { l, err := s.queries.GetLessonByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.Lesson{}, pgx.ErrNoRows } return domain.Lesson{}, err } out := lessonToDomain(dbgen.Lesson{ ID: l.ID, ModuleID: l.ModuleID, Title: l.Title, VideoUrl: l.VideoUrl, Thumbnail: l.Thumbnail, Description: l.Description, SortOrder: l.SortOrder, PublishStatus: l.PublishStatus, CreatedAt: l.CreatedAt, UpdatedAt: l.UpdatedAt, }) out.HasPractice = l.HasPractice return out, nil } func (s *Store) ListLessonsByModuleID(ctx context.Context, moduleID int64, publishedOnly bool, limit, offset int32) ([]domain.Lesson, int64, error) { rows, err := s.queries.ListLessonsByModuleID(ctx, dbgen.ListLessonsByModuleIDParams{ ModuleID: moduleID, Limit: limit, Offset: offset, PublishedOnly: publishedOnly, }) if err != nil { return nil, 0, err } if len(rows) == 0 { return []domain.Lesson{}, 0, nil } var total int64 out := make([]domain.Lesson, 0, len(rows)) for i, r := range rows { if i == 0 { total = r.TotalCount } lesson := lessonToDomain(dbgen.Lesson{ ID: r.ID, ModuleID: r.ModuleID, Title: r.Title, VideoUrl: r.VideoUrl, Thumbnail: r.Thumbnail, Description: r.Description, SortOrder: r.SortOrder, PublishStatus: r.PublishStatus, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt, }) lesson.HasPractice = r.HasPractice out = append(out, lesson) } return out, total, nil } func (s *Store) UpdateLesson(ctx context.Context, id int64, input domain.UpdateLessonInput) (domain.Lesson, error) { sortParam := optionalInt4Update(input.SortOrder) pubParam := optionalPublishStatusUpdate(input.PublishStatus) var titleText pgtype.Text if input.Title != nil { titleText = pgtype.Text{String: *input.Title, Valid: true} } else { titleText = pgtype.Text{Valid: false} } if input.SortOrder != nil { cur, err := s.GetLessonByID(ctx, id) if err != nil { return domain.Lesson{}, err } oldPos := int32(cur.SortOrder) newPos := int32(*input.SortOrder) if oldPos != newPos { q, tx, err := s.BeginTx(ctx) if err != nil { return domain.Lesson{}, err } defer func() { _ = tx.Rollback(ctx) }() if err := repositionLessonSortOrder(ctx, tx, cur.ModuleID, id, oldPos, newPos); err != nil { return domain.Lesson{}, err } l, err := q.UpdateLesson(ctx, dbgen.UpdateLessonParams{ ID: id, Title: titleText, VideoUrl: optionalTextUpdate(input.VideoURL), Thumbnail: optionalTextUpdate(input.Thumbnail), Description: optionalTextUpdate(input.Description), SortOrder: pgtype.Int4{Valid: false}, PublishStatus: pubParam, }) if err != nil { return domain.Lesson{}, err } if err := tx.Commit(ctx); err != nil { return domain.Lesson{}, err } out := lessonToDomain(l) out.HasPractice = cur.HasPractice return out, nil } sortParam = pgtype.Int4{Valid: false} } l, err := s.queries.UpdateLesson(ctx, dbgen.UpdateLessonParams{ ID: id, Title: titleText, VideoUrl: optionalTextUpdate(input.VideoURL), Thumbnail: optionalTextUpdate(input.Thumbnail), Description: optionalTextUpdate(input.Description), SortOrder: sortParam, PublishStatus: pubParam, }) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.Lesson{}, pgx.ErrNoRows } return domain.Lesson{}, err } return lessonToDomain(l), nil } func (s *Store) DeleteLesson(ctx context.Context, id int64) error { return s.queries.DeleteLesson(ctx, id) }