content filtering adjustment
This commit is contained in:
parent
58a94dcd8e
commit
cd8670d5a6
|
|
@ -29,6 +29,33 @@ WHERE qsi.set_id = $1
|
||||||
AND q.status != 'ARCHIVED'
|
AND q.status != 'ARCHIVED'
|
||||||
ORDER BY qsi.display_order;
|
ORDER BY qsi.display_order;
|
||||||
|
|
||||||
|
-- name: GetQuestionSetItemsPaginated :many
|
||||||
|
SELECT
|
||||||
|
COUNT(*) OVER () AS total_count,
|
||||||
|
qsi.id,
|
||||||
|
qsi.set_id,
|
||||||
|
qsi.question_id,
|
||||||
|
qsi.display_order,
|
||||||
|
q.question_text,
|
||||||
|
q.question_type,
|
||||||
|
q.difficulty_level,
|
||||||
|
q.points,
|
||||||
|
q.explanation,
|
||||||
|
q.tips,
|
||||||
|
q.voice_prompt,
|
||||||
|
q.sample_answer_voice_prompt,
|
||||||
|
q.image_url,
|
||||||
|
q.status AS question_status,
|
||||||
|
qaa.correct_answer_text AS audio_correct_answer_text
|
||||||
|
FROM question_set_items qsi
|
||||||
|
JOIN questions q ON q.id = qsi.question_id
|
||||||
|
LEFT JOIN question_audio_answers qaa ON qaa.question_id = q.id
|
||||||
|
WHERE qsi.set_id = $1
|
||||||
|
AND q.status != 'ARCHIVED'
|
||||||
|
ORDER BY qsi.display_order
|
||||||
|
LIMIT sqlc.narg('limit')::INT
|
||||||
|
OFFSET sqlc.narg('offset')::INT;
|
||||||
|
|
||||||
-- name: GetPublishedQuestionsInSet :many
|
-- name: GetPublishedQuestionsInSet :many
|
||||||
SELECT
|
SELECT
|
||||||
qsi.id,
|
qsi.id,
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,96 @@ func (q *Queries) GetQuestionSetItems(ctx context.Context, setID int64) ([]GetQu
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetQuestionSetItemsPaginated = `-- name: GetQuestionSetItemsPaginated :many
|
||||||
|
SELECT
|
||||||
|
COUNT(*) OVER () AS total_count,
|
||||||
|
qsi.id,
|
||||||
|
qsi.set_id,
|
||||||
|
qsi.question_id,
|
||||||
|
qsi.display_order,
|
||||||
|
q.question_text,
|
||||||
|
q.question_type,
|
||||||
|
q.difficulty_level,
|
||||||
|
q.points,
|
||||||
|
q.explanation,
|
||||||
|
q.tips,
|
||||||
|
q.voice_prompt,
|
||||||
|
q.sample_answer_voice_prompt,
|
||||||
|
q.image_url,
|
||||||
|
q.status AS question_status,
|
||||||
|
qaa.correct_answer_text AS audio_correct_answer_text
|
||||||
|
FROM question_set_items qsi
|
||||||
|
JOIN questions q ON q.id = qsi.question_id
|
||||||
|
LEFT JOIN question_audio_answers qaa ON qaa.question_id = q.id
|
||||||
|
WHERE qsi.set_id = $1
|
||||||
|
AND q.status != 'ARCHIVED'
|
||||||
|
ORDER BY qsi.display_order
|
||||||
|
LIMIT $3::INT
|
||||||
|
OFFSET $2::INT
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetQuestionSetItemsPaginatedParams struct {
|
||||||
|
SetID int64 `json:"set_id"`
|
||||||
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
|
Limit pgtype.Int4 `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetQuestionSetItemsPaginatedRow struct {
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SetID int64 `json:"set_id"`
|
||||||
|
QuestionID int64 `json:"question_id"`
|
||||||
|
DisplayOrder int32 `json:"display_order"`
|
||||||
|
QuestionText string `json:"question_text"`
|
||||||
|
QuestionType string `json:"question_type"`
|
||||||
|
DifficultyLevel pgtype.Text `json:"difficulty_level"`
|
||||||
|
Points int32 `json:"points"`
|
||||||
|
Explanation pgtype.Text `json:"explanation"`
|
||||||
|
Tips pgtype.Text `json:"tips"`
|
||||||
|
VoicePrompt pgtype.Text `json:"voice_prompt"`
|
||||||
|
SampleAnswerVoicePrompt pgtype.Text `json:"sample_answer_voice_prompt"`
|
||||||
|
ImageUrl pgtype.Text `json:"image_url"`
|
||||||
|
QuestionStatus string `json:"question_status"`
|
||||||
|
AudioCorrectAnswerText pgtype.Text `json:"audio_correct_answer_text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetQuestionSetItemsPaginated(ctx context.Context, arg GetQuestionSetItemsPaginatedParams) ([]GetQuestionSetItemsPaginatedRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetQuestionSetItemsPaginated, arg.SetID, arg.Offset, arg.Limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetQuestionSetItemsPaginatedRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetQuestionSetItemsPaginatedRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.TotalCount,
|
||||||
|
&i.ID,
|
||||||
|
&i.SetID,
|
||||||
|
&i.QuestionID,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.QuestionText,
|
||||||
|
&i.QuestionType,
|
||||||
|
&i.DifficultyLevel,
|
||||||
|
&i.Points,
|
||||||
|
&i.Explanation,
|
||||||
|
&i.Tips,
|
||||||
|
&i.VoicePrompt,
|
||||||
|
&i.SampleAnswerVoicePrompt,
|
||||||
|
&i.ImageUrl,
|
||||||
|
&i.QuestionStatus,
|
||||||
|
&i.AudioCorrectAnswerText,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const GetQuestionSetsContainingQuestion = `-- name: GetQuestionSetsContainingQuestion :many
|
const GetQuestionSetsContainingQuestion = `-- name: GetQuestionSetsContainingQuestion :many
|
||||||
SELECT qs.id, qs.title, qs.description, qs.set_type, qs.owner_type, qs.owner_id, qs.banner_image, qs.persona, qs.time_limit_minutes, qs.passing_score, qs.shuffle_questions, qs.status, qs.created_at, qs.updated_at, qs.sub_course_video_id, qs.display_order, qs.intro_video_url
|
SELECT qs.id, qs.title, qs.description, qs.set_type, qs.owner_type, qs.owner_id, qs.banner_image, qs.persona, qs.time_limit_minutes, qs.passing_score, qs.shuffle_questions, qs.status, qs.created_at, qs.updated_at, qs.sub_course_video_id, qs.display_order, qs.intro_video_url
|
||||||
FROM question_sets qs
|
FROM question_sets qs
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,9 @@ type QuestionSetItemWithQuestion struct {
|
||||||
Explanation *string
|
Explanation *string
|
||||||
Tips *string
|
Tips *string
|
||||||
VoicePrompt *string
|
VoicePrompt *string
|
||||||
|
SampleAnswerVoicePrompt *string
|
||||||
ImageURL *string
|
ImageURL *string
|
||||||
|
AudioCorrectAnswerText *string
|
||||||
QuestionStatus string
|
QuestionStatus string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ type QuestionStore interface {
|
||||||
// Question Set Items
|
// Question Set Items
|
||||||
AddQuestionToSet(ctx context.Context, setID, questionID int64, displayOrder *int32) (domain.QuestionSetItem, error)
|
AddQuestionToSet(ctx context.Context, setID, questionID int64, displayOrder *int32) (domain.QuestionSetItem, error)
|
||||||
GetQuestionSetItems(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error)
|
GetQuestionSetItems(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error)
|
||||||
|
GetQuestionSetItemsPaginated(ctx context.Context, setID int64, limit, offset int32) ([]domain.QuestionSetItemWithQuestion, int64, error)
|
||||||
GetPublishedQuestionsInSet(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error)
|
GetPublishedQuestionsInSet(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error)
|
||||||
RemoveQuestionFromSet(ctx context.Context, setID, questionID int64) error
|
RemoveQuestionFromSet(ctx context.Context, setID, questionID int64) error
|
||||||
UpdateQuestionOrder(ctx context.Context, setID, questionID int64, displayOrder int32) error
|
UpdateQuestionOrder(ctx context.Context, setID, questionID int64, displayOrder int32) error
|
||||||
|
|
|
||||||
|
|
@ -743,13 +743,54 @@ func (s *Store) GetQuestionSetItems(ctx context.Context, setID int64) ([]domain.
|
||||||
Explanation: fromPgText(r.Explanation),
|
Explanation: fromPgText(r.Explanation),
|
||||||
Tips: fromPgText(r.Tips),
|
Tips: fromPgText(r.Tips),
|
||||||
VoicePrompt: fromPgText(r.VoicePrompt),
|
VoicePrompt: fromPgText(r.VoicePrompt),
|
||||||
|
SampleAnswerVoicePrompt: nil,
|
||||||
ImageURL: fromPgText(r.ImageUrl),
|
ImageURL: fromPgText(r.ImageUrl),
|
||||||
|
AudioCorrectAnswerText: nil,
|
||||||
QuestionStatus: r.QuestionStatus,
|
QuestionStatus: r.QuestionStatus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetQuestionSetItemsPaginated(ctx context.Context, setID int64, limit, offset int32) ([]domain.QuestionSetItemWithQuestion, int64, error) {
|
||||||
|
rows, err := s.queries.GetQuestionSetItemsPaginated(ctx, dbgen.GetQuestionSetItemsPaginatedParams{
|
||||||
|
SetID: setID,
|
||||||
|
Offset: pgtype.Int4{Int32: offset, Valid: true},
|
||||||
|
Limit: pgtype.Int4{Int32: limit, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount int64
|
||||||
|
result := make([]domain.QuestionSetItemWithQuestion, len(rows))
|
||||||
|
for i, r := range rows {
|
||||||
|
if i == 0 {
|
||||||
|
totalCount = r.TotalCount
|
||||||
|
}
|
||||||
|
result[i] = domain.QuestionSetItemWithQuestion{
|
||||||
|
QuestionSetItem: domain.QuestionSetItem{
|
||||||
|
ID: r.ID,
|
||||||
|
SetID: r.SetID,
|
||||||
|
QuestionID: r.QuestionID,
|
||||||
|
DisplayOrder: r.DisplayOrder,
|
||||||
|
},
|
||||||
|
QuestionText: r.QuestionText,
|
||||||
|
QuestionType: r.QuestionType,
|
||||||
|
DifficultyLevel: fromPgText(r.DifficultyLevel),
|
||||||
|
Points: r.Points,
|
||||||
|
Explanation: fromPgText(r.Explanation),
|
||||||
|
Tips: fromPgText(r.Tips),
|
||||||
|
VoicePrompt: fromPgText(r.VoicePrompt),
|
||||||
|
SampleAnswerVoicePrompt: fromPgText(r.SampleAnswerVoicePrompt),
|
||||||
|
ImageURL: fromPgText(r.ImageUrl),
|
||||||
|
AudioCorrectAnswerText: fromPgText(r.AudioCorrectAnswerText),
|
||||||
|
QuestionStatus: r.QuestionStatus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, totalCount, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) GetPublishedQuestionsInSet(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error) {
|
func (s *Store) GetPublishedQuestionsInSet(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error) {
|
||||||
rows, err := s.queries.GetPublishedQuestionsInSet(ctx, setID)
|
rows, err := s.queries.GetPublishedQuestionsInSet(ctx, setID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,10 @@ func (s *Service) GetQuestionSetItems(ctx context.Context, setID int64) ([]domai
|
||||||
return s.questionStore.GetQuestionSetItems(ctx, setID)
|
return s.questionStore.GetQuestionSetItems(ctx, setID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetQuestionSetItemsPaginated(ctx context.Context, setID int64, limit, offset int32) ([]domain.QuestionSetItemWithQuestion, int64, error) {
|
||||||
|
return s.questionStore.GetQuestionSetItemsPaginated(ctx, setID, limit, offset)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) GetPublishedQuestionsInSet(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error) {
|
func (s *Service) GetPublishedQuestionsInSet(ctx context.Context, setID int64) ([]domain.QuestionSetItemWithQuestion, error) {
|
||||||
return s.questionStore.GetPublishedQuestionsInSet(ctx, setID)
|
return s.questionStore.GetPublishedQuestionsInSet(ctx, setID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1053,9 +1053,19 @@ type questionSetItemRes struct {
|
||||||
Explanation *string `json:"explanation,omitempty"`
|
Explanation *string `json:"explanation,omitempty"`
|
||||||
Tips *string `json:"tips,omitempty"`
|
Tips *string `json:"tips,omitempty"`
|
||||||
VoicePrompt *string `json:"voice_prompt,omitempty"`
|
VoicePrompt *string `json:"voice_prompt,omitempty"`
|
||||||
|
SampleAnswerVoicePrompt *string `json:"sample_answer_voice_prompt,omitempty"`
|
||||||
|
ImageURL *string `json:"image_url,omitempty"`
|
||||||
|
AudioCorrectAnswerText *string `json:"audio_correct_answer_text,omitempty"`
|
||||||
QuestionStatus string `json:"question_status"`
|
QuestionStatus string `json:"question_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type paginatedQuestionSetItemsRes struct {
|
||||||
|
Questions []questionSetItemRes `json:"questions"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -1166,6 +1176,9 @@ func (h *Handler) GetQuestionsInSet(c *fiber.Ctx) error {
|
||||||
Explanation: item.Explanation,
|
Explanation: item.Explanation,
|
||||||
Tips: item.Tips,
|
Tips: item.Tips,
|
||||||
VoicePrompt: item.VoicePrompt,
|
VoicePrompt: item.VoicePrompt,
|
||||||
|
SampleAnswerVoicePrompt: item.SampleAnswerVoicePrompt,
|
||||||
|
ImageURL: item.ImageURL,
|
||||||
|
AudioCorrectAnswerText: item.AudioCorrectAnswerText,
|
||||||
QuestionStatus: item.QuestionStatus,
|
QuestionStatus: item.QuestionStatus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1176,6 +1189,106 @@ func (h *Handler) GetQuestionsInSet(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetQuestionsByPractice godoc
|
||||||
|
// @Summary Get questions by practice
|
||||||
|
// @Description Returns paginated questions for a practice(question-set), including AUDIO fields
|
||||||
|
// @Tags question-set-items
|
||||||
|
// @Produce json
|
||||||
|
// @Param practiceId path int true "Practice(question-set) ID"
|
||||||
|
// @Param limit query int false "Limit" default(10)
|
||||||
|
// @Param offset query int false "Offset" default(0)
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 404 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/practices/{practiceId}/questions [get]
|
||||||
|
func (h *Handler) GetQuestionsByPractice(c *fiber.Ctx) error {
|
||||||
|
practiceIDStr := c.Params("practiceId")
|
||||||
|
practiceID, err := strconv.ParseInt(practiceIDStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid practice ID",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
set, err := h.questionsSvc.GetQuestionSetByID(c.Context(), practiceID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Practice not found",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.EqualFold(set.SetType, string(domain.QuestionSetTypePractice)) {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Question set is not a practice",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.enforcePracticeSequenceForStudent(c, set); err != nil {
|
||||||
|
status := fiber.StatusForbidden
|
||||||
|
if ferr, ok := err.(*fiber.Error); ok {
|
||||||
|
status = ferr.Code
|
||||||
|
}
|
||||||
|
return c.Status(status).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Practice is locked",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, _ := strconv.Atoi(c.Query("limit", "10"))
|
||||||
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
if limit > 200 {
|
||||||
|
limit = 200
|
||||||
|
}
|
||||||
|
if offset < 0 {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
items, totalCount, err := h.questionsSvc.GetQuestionSetItemsPaginated(c.Context(), practiceID, int32(limit), int32(offset))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to get practice questions",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
itemResponses := make([]questionSetItemRes, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
itemResponses = append(itemResponses, questionSetItemRes{
|
||||||
|
ID: item.ID,
|
||||||
|
SetID: item.SetID,
|
||||||
|
QuestionID: item.QuestionID,
|
||||||
|
DisplayOrder: item.DisplayOrder,
|
||||||
|
QuestionText: item.QuestionText,
|
||||||
|
QuestionType: item.QuestionType,
|
||||||
|
DifficultyLevel: item.DifficultyLevel,
|
||||||
|
Points: item.Points,
|
||||||
|
Explanation: item.Explanation,
|
||||||
|
Tips: item.Tips,
|
||||||
|
VoicePrompt: item.VoicePrompt,
|
||||||
|
SampleAnswerVoicePrompt: item.SampleAnswerVoicePrompt,
|
||||||
|
ImageURL: item.ImageURL,
|
||||||
|
AudioCorrectAnswerText: item.AudioCorrectAnswerText,
|
||||||
|
QuestionStatus: item.QuestionStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(domain.Response{
|
||||||
|
Message: "Practice questions retrieved successfully",
|
||||||
|
Data: paginatedQuestionSetItemsRes{
|
||||||
|
Questions: itemResponses,
|
||||||
|
TotalCount: totalCount,
|
||||||
|
Limit: int32(limit),
|
||||||
|
Offset: int32(offset),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// CompletePractice godoc
|
// CompletePractice godoc
|
||||||
// @Summary Mark practice as completed
|
// @Summary Mark practice as completed
|
||||||
// @Description Marks a practice question set as completed for the authenticated learner
|
// @Description Marks a practice question set as completed for the authenticated learner
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,7 @@ func (a *App) initAppRoutes() {
|
||||||
// Question Set Items
|
// Question Set Items
|
||||||
groupV1.Post("/question-sets/:setId/questions", a.authMiddleware, a.RequirePermission("question_set_items.add"), h.AddQuestionToSet)
|
groupV1.Post("/question-sets/:setId/questions", a.authMiddleware, a.RequirePermission("question_set_items.add"), h.AddQuestionToSet)
|
||||||
groupV1.Get("/question-sets/:setId/questions", a.authMiddleware, a.RequirePermission("question_set_items.list"), h.GetQuestionsInSet)
|
groupV1.Get("/question-sets/:setId/questions", a.authMiddleware, a.RequirePermission("question_set_items.list"), h.GetQuestionsInSet)
|
||||||
|
groupV1.Get("/practices/:practiceId/questions", a.authMiddleware, a.RequirePermission("question_set_items.list"), h.GetQuestionsByPractice)
|
||||||
groupV1.Delete("/question-sets/:setId/questions/:questionId", a.authMiddleware, a.RequirePermission("question_set_items.remove"), h.RemoveQuestionFromSet)
|
groupV1.Delete("/question-sets/:setId/questions/:questionId", a.authMiddleware, a.RequirePermission("question_set_items.remove"), h.RemoveQuestionFromSet)
|
||||||
groupV1.Put("/question-sets/:setId/questions/:questionId/order", a.authMiddleware, a.RequirePermission("question_set_items.update_order"), h.UpdateQuestionOrderInSet)
|
groupV1.Put("/question-sets/:setId/questions/:questionId/order", a.authMiddleware, a.RequirePermission("question_set_items.update_order"), h.UpdateQuestionOrderInSet)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user