From fffdff10318b573b92cf43dd4ec69d963855e08b Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Wed, 20 May 2026 01:56:15 -0700 Subject: [PATCH] Honor optional sort_order on lesson create under a module. Accept sort_order on CreateLessonInput; SQL falls back to max+1. When set, shift sibling lessons and insert at that position (same pattern as module create). Regenerate sqlc and update Swagger. Co-authored-by: Cursor --- db/query/lms_lessons.sql | 23 +++++++++++----------- docs/docs.go | 3 +++ docs/swagger.json | 3 +++ docs/swagger.yaml | 2 ++ gen/db/lms_lessons.sql.go | 15 +++++++++------ internal/domain/lesson.go | 2 ++ internal/repository/lms_lessons.go | 31 ++++++++++++++++++++++++++++++ 7 files changed, 62 insertions(+), 17 deletions(-) diff --git a/db/query/lms_lessons.sql b/db/query/lms_lessons.sql index e2c7983..b4c4c62 100644 --- a/db/query/lms_lessons.sql +++ b/db/query/lms_lessons.sql @@ -1,17 +1,18 @@ -- name: CreateLesson :one INSERT INTO lessons (module_id, title, video_url, thumbnail, description, sort_order) SELECT - $1, - $2, - $3, - $4, - $5, - coalesce(( - SELECT - max(l.sort_order) - FROM lessons l - WHERE - l.module_id = $1), 0) + 1 + sqlc.arg('module_id'), + sqlc.arg('title'), + sqlc.arg('video_url'), + sqlc.arg('thumbnail'), + sqlc.arg('description'), + COALESCE(sqlc.narg('sort_order')::int, + COALESCE(( + SELECT + max(l.sort_order) + FROM lessons l + WHERE + l.module_id = sqlc.arg('module_id')), 0) + 1) RETURNING *; diff --git a/docs/docs.go b/docs/docs.go index cd3a3bc..77b53fa 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -10484,6 +10484,9 @@ const docTemplate = `{ "description": { "type": "string" }, + "sort_order": { + "type": "integer" + }, "thumbnail": { "type": "string" }, diff --git a/docs/swagger.json b/docs/swagger.json index 6f3287b..cf08c63 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -10476,6 +10476,9 @@ "description": { "type": "string" }, + "sort_order": { + "type": "integer" + }, "thumbnail": { "type": "string" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 625e545..36ea335 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -418,6 +418,8 @@ definitions: properties: description: type: string + sort_order: + type: integer thumbnail: type: string title: diff --git a/gen/db/lms_lessons.sql.go b/gen/db/lms_lessons.sql.go index 280faec..e2fcfe2 100644 --- a/gen/db/lms_lessons.sql.go +++ b/gen/db/lms_lessons.sql.go @@ -19,12 +19,13 @@ SELECT $3, $4, $5, - coalesce(( - SELECT - max(l.sort_order) - FROM lessons l - WHERE - l.module_id = $1), 0) + 1 + COALESCE($6::int, + COALESCE(( + SELECT + max(l.sort_order) + FROM lessons l + WHERE + l.module_id = $1), 0) + 1) RETURNING id, module_id, title, video_url, thumbnail, description, created_at, updated_at, sort_order ` @@ -35,6 +36,7 @@ type CreateLessonParams struct { VideoUrl pgtype.Text `json:"video_url"` Thumbnail pgtype.Text `json:"thumbnail"` Description pgtype.Text `json:"description"` + SortOrder pgtype.Int4 `json:"sort_order"` } func (q *Queries) CreateLesson(ctx context.Context, arg CreateLessonParams) (Lesson, error) { @@ -44,6 +46,7 @@ func (q *Queries) CreateLesson(ctx context.Context, arg CreateLessonParams) (Les arg.VideoUrl, arg.Thumbnail, arg.Description, + arg.SortOrder, ) var i Lesson err := row.Scan( diff --git a/internal/domain/lesson.go b/internal/domain/lesson.go index 7f9c450..69e0b7e 100644 --- a/internal/domain/lesson.go +++ b/internal/domain/lesson.go @@ -22,6 +22,8 @@ type CreateLessonInput struct { VideoURL *string `json:"video_url,omitempty"` Thumbnail *string `json:"thumbnail,omitempty"` Description *string `json:"description,omitempty"` + // SortOrder within the module when set; omit to append after current max within module_id. + SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"` } type UpdateLessonInput struct { diff --git a/internal/repository/lms_lessons.go b/internal/repository/lms_lessons.go index 12aabf8..c88e5cf 100644 --- a/internal/repository/lms_lessons.go +++ b/internal/repository/lms_lessons.go @@ -30,12 +30,43 @@ func lessonToDomain(l dbgen.Lesson) domain.Lesson { } func (s *Store) CreateLesson(ctx context.Context, moduleID int64, input domain.CreateLessonInput) (domain.Lesson, error) { + 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}, + }) + 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}, }) if err != nil { return domain.Lesson{}, err