Honor optional sort_order on course create under a program.
Parses body sort_order, shifts sibling courses in-program, and inserts at the requested slot; omitting it keeps append-after-max behavior. Swagger/sqlc regenerated. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
1136a166f5
commit
37aef49e28
|
|
@ -1,16 +1,17 @@
|
||||||
-- name: CreateCourse :one
|
-- name: CreateCourse :one
|
||||||
INSERT INTO courses (program_id, name, description, thumbnail, sort_order)
|
INSERT INTO courses (program_id, name, description, thumbnail, sort_order)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
sqlc.arg('program_id'),
|
||||||
$2,
|
sqlc.arg('name'),
|
||||||
$3,
|
sqlc.arg('description'),
|
||||||
$4,
|
sqlc.arg('thumbnail'),
|
||||||
coalesce((
|
COALESCE(sqlc.narg('sort_order')::int,
|
||||||
|
COALESCE((
|
||||||
SELECT
|
SELECT
|
||||||
max(c.sort_order)
|
max(c.sort_order)
|
||||||
FROM courses c
|
FROM courses c
|
||||||
WHERE
|
WHERE
|
||||||
c.program_id = $1), 0) + 1
|
c.program_id = sqlc.arg('program_id')), 0) + 1)
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4395,7 +4395,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Create a course under a program",
|
"description": "Create a course under a program. Optional sort_order assigns position within that program (siblings shifted); omit to append after the current highest sort_order in the program.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -10364,6 +10364,11 @@ const docTemplate = `{
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"description": "SortOrder within the program when set; omit to append after current max within program_id (uniqueness is per-program).",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
"thumbnail": {
|
"thumbnail": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4387,7 +4387,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Create a course under a program",
|
"description": "Create a course under a program. Optional sort_order assigns position within that program (siblings shifted); omit to append after the current highest sort_order in the program.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -10356,6 +10356,11 @@
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"description": "SortOrder within the program when set; omit to append after current max within program_id (uniqueness is per-program).",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
"thumbnail": {
|
"thumbnail": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,11 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
sort_order:
|
||||||
|
description: SortOrder within the program when set; omit to append after current
|
||||||
|
max within program_id (uniqueness is per-program).
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
thumbnail:
|
thumbnail:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
|
@ -5372,7 +5377,9 @@ paths:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Create a course under a program
|
description: Create a course under a program. Optional sort_order assigns position
|
||||||
|
within that program (siblings shifted); omit to append after the current highest
|
||||||
|
sort_order in the program.
|
||||||
parameters:
|
parameters:
|
||||||
- description: Program ID
|
- description: Program ID
|
||||||
in: path
|
in: path
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,13 @@ SELECT
|
||||||
$2,
|
$2,
|
||||||
$3,
|
$3,
|
||||||
$4,
|
$4,
|
||||||
coalesce((
|
COALESCE($5::int,
|
||||||
|
COALESCE((
|
||||||
SELECT
|
SELECT
|
||||||
max(c.sort_order)
|
max(c.sort_order)
|
||||||
FROM courses c
|
FROM courses c
|
||||||
WHERE
|
WHERE
|
||||||
c.program_id = $1), 0) + 1
|
c.program_id = $1), 0) + 1)
|
||||||
RETURNING
|
RETURNING
|
||||||
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order
|
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order
|
||||||
`
|
`
|
||||||
|
|
@ -33,6 +34,7 @@ type CreateCourseParams struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Course, error) {
|
func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Course, error) {
|
||||||
|
|
@ -41,6 +43,7 @@ func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Cou
|
||||||
arg.Name,
|
arg.Name,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
|
arg.SortOrder,
|
||||||
)
|
)
|
||||||
var i Course
|
var i Course
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ type CreateCourseInput struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
|
// SortOrder within the program when set; omit to append after current max within program_id (uniqueness is per-program).
|
||||||
|
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCourseInput struct {
|
type UpdateCourseInput struct {
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,41 @@ func courseToDomain(c dbgen.Course) domain.Course {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.CreateCourseInput) (domain.Course, error) {
|
func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.CreateCourseInput) (domain.Course, error) {
|
||||||
|
if input.SortOrder != nil {
|
||||||
|
q, tx, err := s.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
defer func() { _ = tx.Rollback(ctx) }()
|
||||||
|
target := int32(*input.SortOrder)
|
||||||
|
if _, err := tx.Exec(ctx,
|
||||||
|
`UPDATE courses SET sort_order = sort_order + 1 WHERE program_id = $1 AND sort_order >= $2`,
|
||||||
|
programID, target,
|
||||||
|
); err != nil {
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
c, err := q.CreateCourse(ctx, dbgen.CreateCourseParams{
|
||||||
|
ProgramID: programID,
|
||||||
|
Name: input.Name,
|
||||||
|
Description: toPgText(input.Description),
|
||||||
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
|
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
return courseToDomain(c), nil
|
||||||
|
}
|
||||||
|
|
||||||
c, err := s.queries.CreateCourse(ctx, dbgen.CreateCourseParams{
|
c, err := s.queries.CreateCourse(ctx, dbgen.CreateCourseParams{
|
||||||
ProgramID: programID,
|
ProgramID: programID,
|
||||||
Name: input.Name,
|
Name: input.Name,
|
||||||
Description: toPgText(input.Description),
|
Description: toPgText(input.Description),
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Course{}, err
|
return domain.Course{}, err
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
// CreateCourse godoc
|
// CreateCourse godoc
|
||||||
// @Summary Create course
|
// @Summary Create course
|
||||||
// @Description Create a course under a program
|
// @Description Create a course under a program. Optional sort_order assigns position within that program (siblings shifted); omit to append after the current highest sort_order in the program.
|
||||||
// @Tags courses
|
// @Tags courses
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user