Bump sort_order before reassignment for programs, courses, and modules so batch reorder no longer violates uq_courses_program_sort and sibling constraints. Co-authored-by: Cursor <cursoragent@cursor.com>
137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// ReorderPrograms sets sort_order to 1..n in the given order (transactional).
|
|
// Uses an intermediate bump so UNIQUE (sort_order) is never violated mid-reorder.
|
|
func (s *Store) ReorderPrograms(ctx context.Context, orderedIDs []int64) error {
|
|
tx, err := s.conn.Begin(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
|
|
if _, err := tx.Exec(ctx, `
|
|
UPDATE programs
|
|
SET sort_order = sort_order + $1,
|
|
updated_at = CURRENT_TIMESTAMP`, lessonReorderSortBump); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, id := range orderedIDs {
|
|
tag, err := tx.Exec(ctx, `UPDATE programs SET sort_order = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2`, int32(i+1), id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return fmt.Errorf("program id %d not found", id)
|
|
}
|
|
}
|
|
return tx.Commit(ctx)
|
|
}
|
|
|
|
// ReorderCoursesInProgram sets sort_order for courses under programID (transactional).
|
|
// Uses an intermediate bump so UNIQUE (program_id, sort_order) is never violated mid-reorder.
|
|
func (s *Store) ReorderCoursesInProgram(ctx context.Context, programID int64, orderedIDs []int64) error {
|
|
tx, err := s.conn.Begin(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
|
|
if _, err := tx.Exec(ctx, `
|
|
UPDATE courses
|
|
SET sort_order = sort_order + $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE program_id = $2`, lessonReorderSortBump, programID); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, id := range orderedIDs {
|
|
tag, err := tx.Exec(ctx, `
|
|
UPDATE courses
|
|
SET sort_order = $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $2
|
|
AND program_id = $3`, int32(i+1), id, programID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return fmt.Errorf("course id %d not in program %d", id, programID)
|
|
}
|
|
}
|
|
return tx.Commit(ctx)
|
|
}
|
|
|
|
// ReorderModulesInCourse sets sort_order for modules under courseID (transactional).
|
|
// Uses an intermediate bump so UNIQUE (course_id, sort_order) is never violated mid-reorder.
|
|
func (s *Store) ReorderModulesInCourse(ctx context.Context, courseID int64, orderedIDs []int64) error {
|
|
tx, err := s.conn.Begin(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
|
|
if _, err := tx.Exec(ctx, `
|
|
UPDATE modules
|
|
SET sort_order = sort_order + $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE course_id = $2`, lessonReorderSortBump, courseID); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, id := range orderedIDs {
|
|
tag, err := tx.Exec(ctx, `
|
|
UPDATE modules
|
|
SET sort_order = $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $2
|
|
AND course_id = $3`, int32(i+1), id, courseID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return fmt.Errorf("module id %d not in course %d", id, courseID)
|
|
}
|
|
}
|
|
return tx.Commit(ctx)
|
|
}
|
|
|
|
// ReorderLessonsInModule sets sort_order to 1..n under moduleID (transactional).
|
|
// Uses an intermediate bump so UNIQUE (module_id, sort_order) is never violated mid-reorder.
|
|
func (s *Store) ReorderLessonsInModule(ctx context.Context, moduleID int64, orderedIDs []int64) error {
|
|
tx, err := s.conn.Begin(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
|
|
if _, err := tx.Exec(ctx, `
|
|
UPDATE lessons
|
|
SET sort_order = sort_order + $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE module_id = $2`, lessonReorderSortBump, moduleID); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, id := range orderedIDs {
|
|
tag, err := tx.Exec(ctx, `
|
|
UPDATE lessons
|
|
SET sort_order = $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $2
|
|
AND module_id = $3`, int32(i+1), id, moduleID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return fmt.Errorf("lesson id %d not in module %d", id, moduleID)
|
|
}
|
|
}
|
|
return tx.Commit(ctx)
|
|
}
|