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 <cursoragent@cursor.com>
This commit is contained in:
Yared Yemane 2026-05-20 01:56:15 -07:00
parent 7e61e34292
commit fffdff1031
7 changed files with 62 additions and 17 deletions

View File

@ -1,17 +1,18 @@
-- name: CreateLesson :one -- name: CreateLesson :one
INSERT INTO lessons (module_id, title, video_url, thumbnail, description, sort_order) INSERT INTO lessons (module_id, title, video_url, thumbnail, description, sort_order)
SELECT SELECT
$1, sqlc.arg('module_id'),
$2, sqlc.arg('title'),
$3, sqlc.arg('video_url'),
$4, sqlc.arg('thumbnail'),
$5, sqlc.arg('description'),
coalesce(( COALESCE(sqlc.narg('sort_order')::int,
SELECT COALESCE((
max(l.sort_order) SELECT
FROM lessons l max(l.sort_order)
WHERE FROM lessons l
l.module_id = $1), 0) + 1 WHERE
l.module_id = sqlc.arg('module_id')), 0) + 1)
RETURNING RETURNING
*; *;

View File

@ -10484,6 +10484,9 @@ const docTemplate = `{
"description": { "description": {
"type": "string" "type": "string"
}, },
"sort_order": {
"type": "integer"
},
"thumbnail": { "thumbnail": {
"type": "string" "type": "string"
}, },

View File

@ -10476,6 +10476,9 @@
"description": { "description": {
"type": "string" "type": "string"
}, },
"sort_order": {
"type": "integer"
},
"thumbnail": { "thumbnail": {
"type": "string" "type": "string"
}, },

View File

@ -418,6 +418,8 @@ definitions:
properties: properties:
description: description:
type: string type: string
sort_order:
type: integer
thumbnail: thumbnail:
type: string type: string
title: title:

View File

@ -19,12 +19,13 @@ SELECT
$3, $3,
$4, $4,
$5, $5,
coalesce(( COALESCE($6::int,
SELECT COALESCE((
max(l.sort_order) SELECT
FROM lessons l max(l.sort_order)
WHERE FROM lessons l
l.module_id = $1), 0) + 1 WHERE
l.module_id = $1), 0) + 1)
RETURNING RETURNING
id, module_id, title, video_url, thumbnail, description, created_at, updated_at, sort_order 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"` VideoUrl pgtype.Text `json:"video_url"`
Thumbnail pgtype.Text `json:"thumbnail"` Thumbnail pgtype.Text `json:"thumbnail"`
Description pgtype.Text `json:"description"` Description pgtype.Text `json:"description"`
SortOrder pgtype.Int4 `json:"sort_order"`
} }
func (q *Queries) CreateLesson(ctx context.Context, arg CreateLessonParams) (Lesson, error) { 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.VideoUrl,
arg.Thumbnail, arg.Thumbnail,
arg.Description, arg.Description,
arg.SortOrder,
) )
var i Lesson var i Lesson
err := row.Scan( err := row.Scan(

View File

@ -22,6 +22,8 @@ type CreateLessonInput struct {
VideoURL *string `json:"video_url,omitempty"` VideoURL *string `json:"video_url,omitempty"`
Thumbnail *string `json:"thumbnail,omitempty"` Thumbnail *string `json:"thumbnail,omitempty"`
Description *string `json:"description,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 { type UpdateLessonInput struct {

View File

@ -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) { 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{ l, err := s.queries.CreateLesson(ctx, dbgen.CreateLessonParams{
ModuleID: moduleID, ModuleID: moduleID,
Title: input.Title, Title: input.Title,
VideoUrl: toPgText(input.VideoURL), VideoUrl: toPgText(input.VideoURL),
Thumbnail: toPgText(input.Thumbnail), Thumbnail: toPgText(input.Thumbnail),
Description: toPgText(input.Description), Description: toPgText(input.Description),
SortOrder: pgtype.Int4{Valid: false},
}) })
if err != nil { if err != nil {
return domain.Lesson{}, err return domain.Lesson{}, err