Honor optional sort_order on module create under a course.
Accept sort_order in CreateModuleInput, shift siblings when set, and default to max+1 when omitted. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
12ad59c409
commit
83db13bed0
|
|
@ -1,17 +1,18 @@
|
||||||
-- name: CreateModule :one
|
-- name: CreateModule :one
|
||||||
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order)
|
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
sqlc.arg('program_id'),
|
||||||
$2,
|
sqlc.arg('course_id'),
|
||||||
$3,
|
sqlc.arg('name'),
|
||||||
$4,
|
sqlc.arg('description'),
|
||||||
$5,
|
sqlc.arg('icon'),
|
||||||
coalesce((
|
COALESCE(sqlc.narg('sort_order')::int,
|
||||||
|
COALESCE((
|
||||||
SELECT
|
SELECT
|
||||||
max(m.sort_order)
|
max(m.sort_order)
|
||||||
FROM modules m
|
FROM modules m
|
||||||
WHERE
|
WHERE
|
||||||
m.course_id = $2), 0) + 1
|
m.course_id = sqlc.arg('course_id')), 0) + 1)
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,13 @@ SELECT
|
||||||
$3,
|
$3,
|
||||||
$4,
|
$4,
|
||||||
$5,
|
$5,
|
||||||
coalesce((
|
COALESCE($6::int,
|
||||||
|
COALESCE((
|
||||||
SELECT
|
SELECT
|
||||||
max(m.sort_order)
|
max(m.sort_order)
|
||||||
FROM modules m
|
FROM modules m
|
||||||
WHERE
|
WHERE
|
||||||
m.course_id = $2), 0) + 1
|
m.course_id = $2), 0) + 1)
|
||||||
RETURNING
|
RETURNING
|
||||||
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order
|
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order
|
||||||
`
|
`
|
||||||
|
|
@ -35,6 +36,7 @@ type CreateModuleParams struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
Icon pgtype.Text `json:"icon"`
|
Icon pgtype.Text `json:"icon"`
|
||||||
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) {
|
func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) {
|
||||||
|
|
@ -44,6 +46,7 @@ func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Mod
|
||||||
arg.Name,
|
arg.Name,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.Icon,
|
arg.Icon,
|
||||||
|
arg.SortOrder,
|
||||||
)
|
)
|
||||||
var i Module
|
var i Module
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ type CreateModuleInput struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
Icon *string `json:"icon,omitempty"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
|
// SortOrder within the course when set; omit to append after current max within course_id (uniqueness is per-course).
|
||||||
|
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateModuleInput struct {
|
type UpdateModuleInput struct {
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,43 @@ func moduleToDomain(m dbgen.Module) domain.Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) CreateModule(ctx context.Context, programID, courseID int64, input domain.CreateModuleInput) (domain.Module, error) {
|
func (s *Store) CreateModule(ctx context.Context, programID, courseID int64, input domain.CreateModuleInput) (domain.Module, error) {
|
||||||
|
if input.SortOrder != nil {
|
||||||
|
q, tx, err := s.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Module{}, err
|
||||||
|
}
|
||||||
|
defer func() { _ = tx.Rollback(ctx) }()
|
||||||
|
target := int32(*input.SortOrder)
|
||||||
|
if _, err := tx.Exec(ctx,
|
||||||
|
`UPDATE modules SET sort_order = sort_order + 1 WHERE course_id = $1 AND sort_order >= $2`,
|
||||||
|
courseID, target,
|
||||||
|
); err != nil {
|
||||||
|
return domain.Module{}, err
|
||||||
|
}
|
||||||
|
m, err := q.CreateModule(ctx, dbgen.CreateModuleParams{
|
||||||
|
ProgramID: programID,
|
||||||
|
CourseID: courseID,
|
||||||
|
Name: input.Name,
|
||||||
|
Description: toPgText(input.Description),
|
||||||
|
Icon: toPgText(input.Icon),
|
||||||
|
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Module{}, err
|
||||||
|
}
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return domain.Module{}, err
|
||||||
|
}
|
||||||
|
return moduleToDomain(m), nil
|
||||||
|
}
|
||||||
|
|
||||||
m, err := s.queries.CreateModule(ctx, dbgen.CreateModuleParams{
|
m, err := s.queries.CreateModule(ctx, dbgen.CreateModuleParams{
|
||||||
ProgramID: programID,
|
ProgramID: programID,
|
||||||
CourseID: courseID,
|
CourseID: courseID,
|
||||||
Name: input.Name,
|
Name: input.Name,
|
||||||
Description: toPgText(input.Description),
|
Description: toPgText(input.Description),
|
||||||
Icon: toPgText(input.Icon),
|
Icon: toPgText(input.Icon),
|
||||||
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Module{}, err
|
return domain.Module{}, err
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user