feat: auto-assign and return display_order for question set items

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Yared Yemane 2026-06-12 01:38:27 -07:00
parent 8409f69d56
commit c9f4a9c428
4 changed files with 48 additions and 15 deletions

View File

@ -4,7 +4,18 @@ INSERT INTO question_set_items (
question_id, question_id,
display_order display_order
) )
VALUES ($1, $2, COALESCE($3, 0)) VALUES (
sqlc.arg('set_id'),
sqlc.arg('question_id'),
COALESCE(
sqlc.narg('display_order')::int,
COALESCE((
SELECT max(qsi.display_order)
FROM question_set_items AS qsi
WHERE qsi.set_id = sqlc.arg('set_id')
), 0) + 1
)
)
ON CONFLICT (set_id, question_id) DO UPDATE SET display_order = EXCLUDED.display_order ON CONFLICT (set_id, question_id) DO UPDATE SET display_order = EXCLUDED.display_order
RETURNING *; RETURNING *;

View File

@ -17,19 +17,30 @@ INSERT INTO question_set_items (
question_id, question_id,
display_order display_order
) )
VALUES ($1, $2, COALESCE($3, 0)) VALUES (
$1,
$2,
COALESCE(
$3::int,
COALESCE((
SELECT max(qsi.display_order)
FROM question_set_items AS qsi
WHERE qsi.set_id = $1
), 0) + 1
)
)
ON CONFLICT (set_id, question_id) DO UPDATE SET display_order = EXCLUDED.display_order ON CONFLICT (set_id, question_id) DO UPDATE SET display_order = EXCLUDED.display_order
RETURNING id, set_id, question_id, display_order, created_at RETURNING id, set_id, question_id, display_order, created_at
` `
type AddQuestionToSetParams struct { type AddQuestionToSetParams struct {
SetID int64 `json:"set_id"` SetID int64 `json:"set_id"`
QuestionID int64 `json:"question_id"` QuestionID int64 `json:"question_id"`
Column3 interface{} `json:"column_3"` DisplayOrder pgtype.Int4 `json:"display_order"`
} }
func (q *Queries) AddQuestionToSet(ctx context.Context, arg AddQuestionToSetParams) (QuestionSetItem, error) { func (q *Queries) AddQuestionToSet(ctx context.Context, arg AddQuestionToSetParams) (QuestionSetItem, error) {
row := q.db.QueryRow(ctx, AddQuestionToSet, arg.SetID, arg.QuestionID, arg.Column3) row := q.db.QueryRow(ctx, AddQuestionToSet, arg.SetID, arg.QuestionID, arg.DisplayOrder)
var i QuestionSetItem var i QuestionSetItem
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,

View File

@ -1096,15 +1096,15 @@ func (s *Store) DeleteQuestionSet(ctx context.Context, id int64) error {
} }
func (s *Store) AddQuestionToSet(ctx context.Context, setID, questionID int64, displayOrder *int32) (domain.QuestionSetItem, error) { func (s *Store) AddQuestionToSet(ctx context.Context, setID, questionID int64, displayOrder *int32) (domain.QuestionSetItem, error) {
var order interface{} orderParam := pgtype.Int4{Valid: false}
if displayOrder != nil { if displayOrder != nil {
order = *displayOrder orderParam = pgtype.Int4{Int32: *displayOrder, Valid: true}
} }
item, err := s.queries.AddQuestionToSet(ctx, dbgen.AddQuestionToSetParams{ item, err := s.queries.AddQuestionToSet(ctx, dbgen.AddQuestionToSetParams{
SetID: setID, SetID: setID,
QuestionID: questionID, QuestionID: questionID,
Column3: order, DisplayOrder: orderParam,
}) })
if err != nil { if err != nil {
return domain.QuestionSetItem{}, err return domain.QuestionSetItem{}, err

View File

@ -58,6 +58,7 @@ type shortAnswerRes struct {
type questionRes struct { type questionRes struct {
ID int64 `json:"id"` ID int64 `json:"id"`
DisplayOrder *int32 `json:"display_order,omitempty"`
QuestionText *string `json:"question_text,omitempty"` QuestionText *string `json:"question_text,omitempty"`
QuestionType string `json:"question_type"` QuestionType string `json:"question_type"`
QuestionTypeDefinitionID *int64 `json:"question_type_definition_id,omitempty"` QuestionTypeDefinitionID *int64 `json:"question_type_definition_id,omitempty"`
@ -1264,8 +1265,9 @@ func (h *Handler) DeleteQuestionSet(c *fiber.Ctx) error {
// Question Set Items // Question Set Items
type addQuestionToSetReq struct { type addQuestionToSetReq struct {
QuestionID int64 `json:"question_id" validate:"required"` QuestionID int64 `json:"question_id" validate:"required"`
DisplayOrder *int32 `json:"display_order"` // Omit to append after the current highest display_order in the set; set explicitly to insert at a position.
DisplayOrder *int32 `json:"display_order,omitempty" validate:"omitempty,min=0"`
} }
type questionSetItemRes struct { type questionSetItemRes struct {
@ -1321,7 +1323,7 @@ func questionSetItemsToRes(items []domain.QuestionSetItemWithQuestion) []questio
// AddQuestionToSet godoc // AddQuestionToSet godoc
// @Summary Add question to set // @Summary Add question to set
// @Description Links a question to a question set // @Description Links a question to a question set. Omit display_order to append after the current max; the assigned value is returned in the response.
// @Tags question-set-items // @Tags question-set-items
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -1348,6 +1350,12 @@ func (h *Handler) AddQuestionToSet(c *fiber.Ctx) error {
Error: err.Error(), Error: err.Error(),
}) })
} }
if valErrs, ok := h.validator.Validate(c, req); !ok {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Validation failed",
Error: firstValidationError(valErrs),
})
}
item, err := h.questionsSvc.AddQuestionToSet(c.Context(), setID, req.QuestionID, req.DisplayOrder) item, err := h.questionsSvc.AddQuestionToSet(c.Context(), setID, req.QuestionID, req.DisplayOrder)
if err != nil { if err != nil {
@ -1487,7 +1495,10 @@ func (h *Handler) GetQuestionsInSet(c *fiber.Ctx) error {
}) })
} }
questionResponses = append(questionResponses, buildQuestionRes(question, catalog)) res := buildQuestionRes(question, catalog)
order := item.DisplayOrder
res.DisplayOrder = &order
questionResponses = append(questionResponses, res)
} }
return c.JSON(domain.Response{ return c.JSON(domain.Response{