Compare commits
No commits in common. "7ff0b639cfbffd7d2af78291cea62433aee65b82" and "1026354c24d692fe37c7014a389551bd81d7a283" have entirely different histories.
7ff0b639cf
...
1026354c24
|
|
@ -1,18 +0,0 @@
|
||||||
-- Restores legacy lesson columns. Rows will have NULL question_set_id until repopulated.
|
|
||||||
|
|
||||||
ALTER TABLE sub_module_lessons
|
|
||||||
ADD COLUMN IF NOT EXISTS question_set_id BIGINT REFERENCES question_sets(id) ON DELETE CASCADE,
|
|
||||||
ADD COLUMN IF NOT EXISTS intro_video_url TEXT;
|
|
||||||
|
|
||||||
UPDATE sub_module_lessons
|
|
||||||
SET intro_video_url = teaching_video_url
|
|
||||||
WHERE teaching_video_url IS NOT NULL;
|
|
||||||
|
|
||||||
ALTER TABLE sub_module_lessons
|
|
||||||
DROP COLUMN IF EXISTS title,
|
|
||||||
DROP COLUMN IF EXISTS description,
|
|
||||||
DROP COLUMN IF EXISTS thumbnail,
|
|
||||||
DROP COLUMN IF EXISTS teaching_text,
|
|
||||||
DROP COLUMN IF EXISTS teaching_image_url,
|
|
||||||
DROP COLUMN IF EXISTS teaching_audio_url,
|
|
||||||
DROP COLUMN IF EXISTS teaching_video_url;
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
-- Lessons are teaching content only (text, images, audio, video, thumbnail).
|
|
||||||
-- Question sets remain linked to practices, not lessons.
|
|
||||||
|
|
||||||
ALTER TABLE sub_module_lessons
|
|
||||||
ADD COLUMN IF NOT EXISTS title VARCHAR(255),
|
|
||||||
ADD COLUMN IF NOT EXISTS description TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS thumbnail TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS teaching_text TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS teaching_image_url TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS teaching_audio_url TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS teaching_video_url TEXT;
|
|
||||||
|
|
||||||
UPDATE sub_module_lessons sml
|
|
||||||
SET
|
|
||||||
title = qs.title,
|
|
||||||
description = qs.description
|
|
||||||
FROM question_sets qs
|
|
||||||
WHERE sml.question_set_id IS NOT NULL
|
|
||||||
AND qs.id = sml.question_set_id;
|
|
||||||
|
|
||||||
UPDATE sub_module_lessons
|
|
||||||
SET title = 'Lesson'
|
|
||||||
WHERE title IS NULL OR trim(title) = '';
|
|
||||||
|
|
||||||
UPDATE sub_module_lessons
|
|
||||||
SET teaching_video_url = intro_video_url
|
|
||||||
WHERE intro_video_url IS NOT NULL;
|
|
||||||
|
|
||||||
ALTER TABLE sub_module_lessons DROP CONSTRAINT IF EXISTS sub_module_lessons_question_set_id_fkey;
|
|
||||||
ALTER TABLE sub_module_lessons DROP CONSTRAINT IF EXISTS sub_module_lessons_question_set_id_key;
|
|
||||||
|
|
||||||
ALTER TABLE sub_module_lessons DROP COLUMN IF EXISTS question_set_id;
|
|
||||||
ALTER TABLE sub_module_lessons DROP COLUMN IF EXISTS intro_video_url;
|
|
||||||
|
|
||||||
ALTER TABLE sub_module_lessons
|
|
||||||
ALTER COLUMN title SET NOT NULL,
|
|
||||||
ALTER COLUMN title SET DEFAULT 'Lesson';
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
ALTER TABLE levels
|
|
||||||
DROP COLUMN IF EXISTS title,
|
|
||||||
DROP COLUMN IF EXISTS description,
|
|
||||||
DROP COLUMN IF EXISTS thumbnail;
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
ALTER TABLE levels
|
|
||||||
ADD COLUMN IF NOT EXISTS title VARCHAR(255),
|
|
||||||
ADD COLUMN IF NOT EXISTS description TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS thumbnail TEXT;
|
|
||||||
|
|
||||||
UPDATE levels
|
|
||||||
SET title = cefr_level
|
|
||||||
WHERE title IS NULL OR trim(title) = '';
|
|
||||||
|
|
||||||
ALTER TABLE levels
|
|
||||||
ALTER COLUMN title SET NOT NULL;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
DROP INDEX IF EXISTS idx_sub_module_capstones_sub_module_id;
|
|
||||||
DROP TABLE IF EXISTS sub_module_capstones;
|
|
||||||
|
|
||||||
ALTER TABLE question_sets DROP CONSTRAINT IF EXISTS question_sets_set_type_check;
|
|
||||||
ALTER TABLE question_sets ADD CONSTRAINT question_sets_set_type_check
|
|
||||||
CHECK (set_type IN (
|
|
||||||
'PRACTICE',
|
|
||||||
'INITIAL_ASSESSMENT',
|
|
||||||
'QUIZ',
|
|
||||||
'EXAM',
|
|
||||||
'SURVEY'
|
|
||||||
));
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
-- Capstone assessments: sub-module scoped, backed by question_sets (type CAPSTONE).
|
|
||||||
|
|
||||||
ALTER TABLE question_sets DROP CONSTRAINT IF EXISTS question_sets_set_type_check;
|
|
||||||
ALTER TABLE question_sets ADD CONSTRAINT question_sets_set_type_check
|
|
||||||
CHECK (set_type IN (
|
|
||||||
'PRACTICE',
|
|
||||||
'INITIAL_ASSESSMENT',
|
|
||||||
'QUIZ',
|
|
||||||
'EXAM',
|
|
||||||
'SURVEY',
|
|
||||||
'CAPSTONE'
|
|
||||||
));
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS sub_module_capstones (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
sub_module_id BIGINT NOT NULL REFERENCES sub_modules(id) ON DELETE CASCADE,
|
|
||||||
title VARCHAR(255) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
tips TEXT,
|
|
||||||
thumbnail TEXT,
|
|
||||||
question_set_id BIGINT NOT NULL REFERENCES question_sets(id) ON DELETE CASCADE,
|
|
||||||
display_order INT NOT NULL DEFAULT 0,
|
|
||||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE (question_set_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_sub_module_capstones_sub_module_id
|
|
||||||
ON sub_module_capstones (sub_module_id);
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
DROP INDEX IF EXISTS idx_module_capstones_module_id;
|
|
||||||
DROP TABLE IF EXISTS module_capstones;
|
|
||||||
|
|
||||||
ALTER TABLE modules DROP COLUMN IF EXISTS icon_url;
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
ALTER TABLE modules
|
|
||||||
ADD COLUMN IF NOT EXISTS icon_url TEXT;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS module_capstones (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
module_id BIGINT NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
|
||||||
title VARCHAR(255) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
tips TEXT,
|
|
||||||
thumbnail TEXT,
|
|
||||||
question_set_id BIGINT NOT NULL REFERENCES question_sets(id) ON DELETE CASCADE,
|
|
||||||
display_order INT NOT NULL DEFAULT 0,
|
|
||||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE (question_set_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_module_capstones_module_id
|
|
||||||
ON module_capstones (module_id);
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
ALTER TABLE sub_modules
|
|
||||||
DROP COLUMN IF EXISTS tips,
|
|
||||||
DROP COLUMN IF EXISTS thumbnail;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
ALTER TABLE sub_modules
|
|
||||||
ADD COLUMN IF NOT EXISTS thumbnail TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS tips TEXT;
|
|
||||||
|
|
@ -83,17 +83,43 @@ WHERE sub_module_id = $1
|
||||||
ORDER BY display_order ASC, id ASC;
|
ORDER BY display_order ASC, id ASC;
|
||||||
|
|
||||||
-- name: GetSubModuleLessons :many
|
-- name: GetSubModuleLessons :many
|
||||||
SELECT *
|
SELECT
|
||||||
FROM sub_module_lessons
|
smp.id,
|
||||||
WHERE sub_module_id = $1
|
smp.sub_module_id,
|
||||||
AND is_active = TRUE
|
smp.question_set_id,
|
||||||
ORDER BY display_order ASC, id ASC;
|
smp.intro_video_url,
|
||||||
|
smp.display_order,
|
||||||
|
smp.is_active,
|
||||||
|
qs.title,
|
||||||
|
qs.description,
|
||||||
|
qs.status,
|
||||||
|
qs.set_type,
|
||||||
|
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
|
||||||
|
FROM sub_module_lessons smp
|
||||||
|
JOIN question_sets qs ON qs.id = smp.question_set_id
|
||||||
|
WHERE smp.sub_module_id = $1
|
||||||
|
AND smp.is_active = TRUE
|
||||||
|
AND qs.set_type = 'QUIZ'
|
||||||
|
ORDER BY smp.display_order ASC, smp.id ASC;
|
||||||
|
|
||||||
-- name: GetSubModuleLessonByID :one
|
-- name: GetSubModuleLessonByID :one
|
||||||
SELECT *
|
SELECT
|
||||||
FROM sub_module_lessons
|
smp.id,
|
||||||
WHERE id = $1
|
smp.sub_module_id,
|
||||||
AND is_active = TRUE;
|
smp.question_set_id,
|
||||||
|
smp.intro_video_url,
|
||||||
|
smp.display_order,
|
||||||
|
smp.is_active,
|
||||||
|
qs.title,
|
||||||
|
qs.description,
|
||||||
|
qs.status,
|
||||||
|
qs.set_type,
|
||||||
|
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
|
||||||
|
FROM sub_module_lessons smp
|
||||||
|
JOIN question_sets qs ON qs.id = smp.question_set_id
|
||||||
|
WHERE smp.id = $1
|
||||||
|
AND smp.is_active = TRUE
|
||||||
|
AND qs.set_type = 'QUIZ';
|
||||||
|
|
||||||
-- name: GetSubModulePractices :many
|
-- name: GetSubModulePractices :many
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -136,71 +162,16 @@ WHERE smp.id = $1
|
||||||
AND smp.is_active = TRUE
|
AND smp.is_active = TRUE
|
||||||
AND qs.set_type = 'PRACTICE';
|
AND qs.set_type = 'PRACTICE';
|
||||||
|
|
||||||
-- name: GetSubModuleCapstones :many
|
|
||||||
SELECT
|
|
||||||
smc.id,
|
|
||||||
smc.sub_module_id,
|
|
||||||
smc.title,
|
|
||||||
smc.description,
|
|
||||||
smc.tips,
|
|
||||||
smc.thumbnail,
|
|
||||||
smc.question_set_id,
|
|
||||||
smc.display_order,
|
|
||||||
smc.is_active,
|
|
||||||
qs.status,
|
|
||||||
qs.set_type,
|
|
||||||
qs.time_limit_minutes,
|
|
||||||
qs.passing_score,
|
|
||||||
qs.shuffle_questions,
|
|
||||||
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
|
|
||||||
FROM sub_module_capstones smc
|
|
||||||
JOIN question_sets qs ON qs.id = smc.question_set_id
|
|
||||||
WHERE smc.sub_module_id = $1
|
|
||||||
AND smc.is_active = TRUE
|
|
||||||
AND qs.set_type = 'CAPSTONE'
|
|
||||||
ORDER BY smc.display_order ASC, smc.id ASC;
|
|
||||||
|
|
||||||
-- name: GetSubModuleCapstoneByID :one
|
|
||||||
SELECT
|
|
||||||
smc.id,
|
|
||||||
smc.sub_module_id,
|
|
||||||
smc.title,
|
|
||||||
smc.description,
|
|
||||||
smc.tips,
|
|
||||||
smc.thumbnail,
|
|
||||||
smc.question_set_id,
|
|
||||||
smc.display_order,
|
|
||||||
smc.is_active,
|
|
||||||
qs.status,
|
|
||||||
qs.set_type,
|
|
||||||
qs.time_limit_minutes,
|
|
||||||
qs.passing_score,
|
|
||||||
qs.shuffle_questions,
|
|
||||||
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
|
|
||||||
FROM sub_module_capstones smc
|
|
||||||
JOIN question_sets qs ON qs.id = smc.question_set_id
|
|
||||||
WHERE smc.id = $1
|
|
||||||
AND smc.is_active = TRUE
|
|
||||||
AND qs.set_type = 'CAPSTONE';
|
|
||||||
|
|
||||||
-- name: GetFullHierarchyByCourseID :many
|
-- name: GetFullHierarchyByCourseID :many
|
||||||
SELECT
|
SELECT
|
||||||
c.id AS course_id,
|
c.id AS course_id,
|
||||||
c.title AS course_title,
|
c.title AS course_title,
|
||||||
l.id AS level_id,
|
l.id AS level_id,
|
||||||
l.cefr_level,
|
l.cefr_level,
|
||||||
l.title AS level_title,
|
|
||||||
l.description AS level_description,
|
|
||||||
l.thumbnail AS level_thumbnail,
|
|
||||||
m.id AS module_id,
|
m.id AS module_id,
|
||||||
m.title AS module_title,
|
m.title AS module_title,
|
||||||
m.icon_url AS module_icon_url,
|
|
||||||
sm.id AS sub_module_id,
|
sm.id AS sub_module_id,
|
||||||
sm.title AS sub_module_title,
|
sm.title AS sub_module_title
|
||||||
sm.description AS sub_module_description,
|
|
||||||
sm.thumbnail AS sub_module_thumbnail,
|
|
||||||
sm.tips AS sub_module_tips,
|
|
||||||
sm.display_order AS sub_module_display_order
|
|
||||||
FROM courses c
|
FROM courses c
|
||||||
LEFT JOIN levels l ON l.course_id = c.id AND l.is_active = TRUE
|
LEFT JOIN levels l ON l.course_id = c.id AND l.is_active = TRUE
|
||||||
LEFT JOIN modules m ON m.level_id = l.id AND m.is_active = TRUE
|
LEFT JOIN modules m ON m.level_id = l.id AND m.is_active = TRUE
|
||||||
|
|
@ -261,24 +232,10 @@ OFFSET sqlc.narg('offset')::INT;
|
||||||
INSERT INTO levels (
|
INSERT INTO levels (
|
||||||
course_id,
|
course_id,
|
||||||
cefr_level,
|
cefr_level,
|
||||||
title,
|
|
||||||
description,
|
|
||||||
thumbnail,
|
|
||||||
display_order,
|
display_order,
|
||||||
is_active
|
is_active
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
|
VALUES ($1, $2, COALESCE($3, 0), COALESCE($4, TRUE))
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: UpdateLevel :one
|
|
||||||
UPDATE levels
|
|
||||||
SET
|
|
||||||
title = $1,
|
|
||||||
description = $2,
|
|
||||||
thumbnail = $3,
|
|
||||||
display_order = $4,
|
|
||||||
is_active = $5
|
|
||||||
WHERE id = $6
|
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateModule :one
|
-- name: CreateModule :one
|
||||||
|
|
@ -286,22 +243,10 @@ INSERT INTO modules (
|
||||||
level_id,
|
level_id,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
icon_url,
|
|
||||||
display_order,
|
display_order,
|
||||||
is_active
|
is_active
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, COALESCE($5, 0), COALESCE($6, TRUE))
|
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE))
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: UpdateModule :one
|
|
||||||
UPDATE modules
|
|
||||||
SET
|
|
||||||
title = $1,
|
|
||||||
description = $2,
|
|
||||||
icon_url = $3,
|
|
||||||
display_order = $4,
|
|
||||||
is_active = $5
|
|
||||||
WHERE id = $6
|
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateSubModule :one
|
-- name: CreateSubModule :one
|
||||||
|
|
@ -309,24 +254,10 @@ INSERT INTO sub_modules (
|
||||||
module_id,
|
module_id,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
thumbnail,
|
|
||||||
tips,
|
|
||||||
display_order,
|
display_order,
|
||||||
is_active
|
is_active
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
|
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE))
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: UpdateSubModule :one
|
|
||||||
UPDATE sub_modules
|
|
||||||
SET
|
|
||||||
title = $1,
|
|
||||||
description = $2,
|
|
||||||
thumbnail = $3,
|
|
||||||
tips = $4,
|
|
||||||
display_order = $5,
|
|
||||||
is_active = $6
|
|
||||||
WHERE id = $7
|
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateSubModuleVideo :one
|
-- name: CreateSubModuleVideo :one
|
||||||
|
|
@ -358,47 +289,26 @@ VALUES (
|
||||||
)
|
)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateSubModuleLesson :one
|
-- name: AttachQuestionSetLessonToSubModule :one
|
||||||
INSERT INTO sub_module_lessons (
|
INSERT INTO sub_module_lessons (
|
||||||
sub_module_id,
|
sub_module_id,
|
||||||
title,
|
question_set_id,
|
||||||
description,
|
intro_video_url,
|
||||||
thumbnail,
|
|
||||||
teaching_text,
|
|
||||||
teaching_image_url,
|
|
||||||
teaching_audio_url,
|
|
||||||
teaching_video_url,
|
|
||||||
display_order,
|
display_order,
|
||||||
is_active
|
is_active
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE))
|
||||||
$1,
|
|
||||||
$2,
|
|
||||||
$3,
|
|
||||||
$4,
|
|
||||||
$5,
|
|
||||||
$6,
|
|
||||||
$7,
|
|
||||||
$8,
|
|
||||||
COALESCE($9, 0),
|
|
||||||
COALESCE($10, TRUE)
|
|
||||||
)
|
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: UpdateSubModuleLesson :one
|
-- name: UpdateSubModuleLesson :one
|
||||||
UPDATE sub_module_lessons
|
UPDATE sub_module_lessons
|
||||||
SET
|
SET
|
||||||
sub_module_id = $1,
|
sub_module_id = $1,
|
||||||
title = $2,
|
question_set_id = $2,
|
||||||
description = $3,
|
intro_video_url = $3,
|
||||||
thumbnail = $4,
|
display_order = $4,
|
||||||
teaching_text = $5,
|
is_active = $5
|
||||||
teaching_image_url = $6,
|
WHERE id = $6
|
||||||
teaching_audio_url = $7,
|
|
||||||
teaching_video_url = $8,
|
|
||||||
display_order = $9,
|
|
||||||
is_active = $10
|
|
||||||
WHERE id = $11
|
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateSubModulePractice :one
|
-- name: CreateSubModulePractice :one
|
||||||
|
|
@ -415,102 +325,3 @@ INSERT INTO sub_module_practices (
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
|
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateSubModuleCapstone :one
|
|
||||||
INSERT INTO sub_module_capstones (
|
|
||||||
sub_module_id,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
tips,
|
|
||||||
thumbnail,
|
|
||||||
question_set_id,
|
|
||||||
display_order,
|
|
||||||
is_active
|
|
||||||
)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
|
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: UpdateSubModuleCapstone :one
|
|
||||||
UPDATE sub_module_capstones
|
|
||||||
SET
|
|
||||||
title = $1,
|
|
||||||
description = $2,
|
|
||||||
tips = $3,
|
|
||||||
thumbnail = $4,
|
|
||||||
display_order = $5,
|
|
||||||
is_active = $6
|
|
||||||
WHERE id = $7
|
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: GetModuleCapstones :many
|
|
||||||
SELECT
|
|
||||||
mc.id,
|
|
||||||
mc.module_id,
|
|
||||||
mc.title,
|
|
||||||
mc.description,
|
|
||||||
mc.tips,
|
|
||||||
mc.thumbnail,
|
|
||||||
mc.question_set_id,
|
|
||||||
mc.display_order,
|
|
||||||
mc.is_active,
|
|
||||||
qs.status,
|
|
||||||
qs.set_type,
|
|
||||||
qs.time_limit_minutes,
|
|
||||||
qs.passing_score,
|
|
||||||
qs.shuffle_questions,
|
|
||||||
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
|
|
||||||
FROM module_capstones mc
|
|
||||||
JOIN question_sets qs ON qs.id = mc.question_set_id
|
|
||||||
WHERE mc.module_id = $1
|
|
||||||
AND mc.is_active = TRUE
|
|
||||||
AND qs.set_type = 'CAPSTONE'
|
|
||||||
ORDER BY mc.display_order ASC, mc.id ASC;
|
|
||||||
|
|
||||||
-- name: GetModuleCapstoneByID :one
|
|
||||||
SELECT
|
|
||||||
mc.id,
|
|
||||||
mc.module_id,
|
|
||||||
mc.title,
|
|
||||||
mc.description,
|
|
||||||
mc.tips,
|
|
||||||
mc.thumbnail,
|
|
||||||
mc.question_set_id,
|
|
||||||
mc.display_order,
|
|
||||||
mc.is_active,
|
|
||||||
qs.status,
|
|
||||||
qs.set_type,
|
|
||||||
qs.time_limit_minutes,
|
|
||||||
qs.passing_score,
|
|
||||||
qs.shuffle_questions,
|
|
||||||
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
|
|
||||||
FROM module_capstones mc
|
|
||||||
JOIN question_sets qs ON qs.id = mc.question_set_id
|
|
||||||
WHERE mc.id = $1
|
|
||||||
AND mc.is_active = TRUE
|
|
||||||
AND qs.set_type = 'CAPSTONE';
|
|
||||||
|
|
||||||
-- name: CreateModuleCapstone :one
|
|
||||||
INSERT INTO module_capstones (
|
|
||||||
module_id,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
tips,
|
|
||||||
thumbnail,
|
|
||||||
question_set_id,
|
|
||||||
display_order,
|
|
||||||
is_active
|
|
||||||
)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
|
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: UpdateModuleCapstone :one
|
|
||||||
UPDATE module_capstones
|
|
||||||
SET
|
|
||||||
title = $1,
|
|
||||||
description = $2,
|
|
||||||
tips = $3,
|
|
||||||
thumbnail = $4,
|
|
||||||
display_order = $5,
|
|
||||||
is_active = $6
|
|
||||||
WHERE id = $7
|
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
|
|
|
||||||
1797
docs/docs.go
1797
docs/docs.go
File diff suppressed because it is too large
Load Diff
1797
docs/swagger.json
1797
docs/swagger.json
File diff suppressed because it is too large
Load Diff
1207
docs/swagger.yaml
1207
docs/swagger.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
func (q *Queries) GetSubModuleByIDCompat(ctx context.Context, id int64) (SubModule, error) {
|
func (q *Queries) GetSubModuleByIDCompat(ctx context.Context, id int64) (SubModule, error) {
|
||||||
row := q.db.QueryRow(ctx, `
|
row := q.db.QueryRow(ctx, `
|
||||||
SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id, thumbnail, tips
|
SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id
|
||||||
FROM sub_modules
|
FROM sub_modules
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`, id)
|
`, id)
|
||||||
|
|
@ -22,24 +22,19 @@ WHERE id = $1
|
||||||
&i.IsActive,
|
&i.IsActive,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.LegacySubCourseID,
|
&i.LegacySubCourseID,
|
||||||
&i.Thumbnail,
|
|
||||||
&i.Tips,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateSubModuleCompat(ctx context.Context, id int64, title string, description string, thumbnail string, tips string, displayOrder int32, isActive bool) error {
|
func (q *Queries) UpdateSubModuleCompat(ctx context.Context, id int64, title string, description string, isActive bool) error {
|
||||||
_, err := q.db.Exec(ctx, `
|
_, err := q.db.Exec(ctx, `
|
||||||
UPDATE sub_modules
|
UPDATE sub_modules
|
||||||
SET
|
SET
|
||||||
title = $1,
|
title = $1,
|
||||||
description = NULLIF($2, ''),
|
description = NULLIF($2, ''),
|
||||||
thumbnail = NULLIF($3, ''),
|
is_active = $3
|
||||||
tips = NULLIF($4, ''),
|
WHERE id = $4
|
||||||
display_order = $5,
|
`, title, description, isActive, id)
|
||||||
is_active = $6
|
|
||||||
WHERE id = $7
|
|
||||||
`, title, description, thumbnail, tips, displayOrder, isActive, id)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,24 +119,6 @@ func (q *Queries) DeletePracticeCompat(ctx context.Context, id int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCapstoneCompat removes the backing question set (and cascades sub_module_capstones).
|
|
||||||
func (q *Queries) DeleteCapstoneCompat(ctx context.Context, capstoneID int64) error {
|
|
||||||
_, err := q.db.Exec(ctx, `
|
|
||||||
DELETE FROM question_sets
|
|
||||||
WHERE id = (SELECT question_set_id FROM sub_module_capstones WHERE id = $1)
|
|
||||||
`, capstoneID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteModuleCapstoneCompat removes the backing question set (and cascades module_capstones).
|
|
||||||
func (q *Queries) DeleteModuleCapstoneCompat(ctx context.Context, capstoneID int64) error {
|
|
||||||
_, err := q.db.Exec(ctx, `
|
|
||||||
DELETE FROM question_sets
|
|
||||||
WHERE id = (SELECT question_set_id FROM module_capstones WHERE id = $1)
|
|
||||||
`, capstoneID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) CreateCourseCompat(
|
func (q *Queries) CreateCourseCompat(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
categoryID int64,
|
categoryID int64,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -76,9 +76,6 @@ type Level struct {
|
||||||
DisplayOrder int32 `json:"display_order"`
|
DisplayOrder int32 `json:"display_order"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
Title string `json:"title"`
|
|
||||||
Description pgtype.Text `json:"description"`
|
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LevelToSubCourse struct {
|
type LevelToSubCourse struct {
|
||||||
|
|
@ -94,20 +91,6 @@ type Module struct {
|
||||||
DisplayOrder int32 `json:"display_order"`
|
DisplayOrder int32 `json:"display_order"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
IconUrl pgtype.Text `json:"icon_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModuleCapstone struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
ModuleID int64 `json:"module_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description pgtype.Text `json:"description"`
|
|
||||||
Tips pgtype.Text `json:"tips"`
|
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
|
||||||
DisplayOrder int32 `json:"display_order"`
|
|
||||||
IsActive bool `json:"is_active"`
|
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModuleToSubCourse struct {
|
type ModuleToSubCourse struct {
|
||||||
|
|
@ -370,36 +353,16 @@ type SubModule struct {
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
LegacySubCourseID pgtype.Int8 `json:"legacy_sub_course_id"`
|
LegacySubCourseID pgtype.Int8 `json:"legacy_sub_course_id"`
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
|
||||||
Tips pgtype.Text `json:"tips"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubModuleCapstone struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
SubModuleID int64 `json:"sub_module_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description pgtype.Text `json:"description"`
|
|
||||||
Tips pgtype.Text `json:"tips"`
|
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
|
||||||
DisplayOrder int32 `json:"display_order"`
|
|
||||||
IsActive bool `json:"is_active"`
|
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubModuleLesson struct {
|
type SubModuleLesson struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
SubModuleID int64 `json:"sub_module_id"`
|
SubModuleID int64 `json:"sub_module_id"`
|
||||||
DisplayOrder int32 `json:"display_order"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
IsActive bool `json:"is_active"`
|
IntroVideoUrl pgtype.Text `json:"intro_video_url"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
DisplayOrder int32 `json:"display_order"`
|
||||||
Title string `json:"title"`
|
IsActive bool `json:"is_active"`
|
||||||
Description pgtype.Text `json:"description"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
|
||||||
TeachingText pgtype.Text `json:"teaching_text"`
|
|
||||||
TeachingImageUrl pgtype.Text `json:"teaching_image_url"`
|
|
||||||
TeachingAudioUrl pgtype.Text `json:"teaching_audio_url"`
|
|
||||||
TeachingVideoUrl pgtype.Text `json:"teaching_video_url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubModulePractice struct {
|
type SubModulePractice struct {
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,6 @@ type LearningPathSubCourse struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
Tips *string `json:"tips,omitempty"`
|
|
||||||
DisplayOrder int32 `json:"display_order"`
|
DisplayOrder int32 `json:"display_order"`
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
SubLevel string `json:"sub_level"`
|
SubLevel string `json:"sub_level"`
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ const (
|
||||||
QuestionSetTypeQuiz QuestionSetType = "QUIZ"
|
QuestionSetTypeQuiz QuestionSetType = "QUIZ"
|
||||||
QuestionSetTypeExam QuestionSetType = "EXAM"
|
QuestionSetTypeExam QuestionSetType = "EXAM"
|
||||||
QuestionSetTypeSurvey QuestionSetType = "SURVEY"
|
QuestionSetTypeSurvey QuestionSetType = "SURVEY"
|
||||||
QuestionSetTypeCapstone QuestionSetType = "CAPSTONE"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PracticeAccessBlock struct {
|
type PracticeAccessBlock struct {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -515,7 +515,7 @@ func (h *Handler) DeleteQuestion(c *fiber.Ctx) error {
|
||||||
type createQuestionSetReq struct {
|
type createQuestionSetReq struct {
|
||||||
Title string `json:"title" validate:"required"`
|
Title string `json:"title" validate:"required"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
SetType string `json:"set_type" validate:"required,oneof=PRACTICE INITIAL_ASSESSMENT QUIZ EXAM SURVEY CAPSTONE"`
|
SetType string `json:"set_type" validate:"required,oneof=PRACTICE INITIAL_ASSESSMENT QUIZ EXAM SURVEY"`
|
||||||
OwnerType *string `json:"owner_type"`
|
OwnerType *string `json:"owner_type"`
|
||||||
OwnerID *int64 `json:"owner_id"`
|
OwnerID *int64 `json:"owner_id"`
|
||||||
BannerImage *string `json:"banner_image"`
|
BannerImage *string `json:"banner_image"`
|
||||||
|
|
@ -791,7 +791,7 @@ func (h *Handler) GetQuestionSetByID(c *fiber.Ctx) error {
|
||||||
// @Description Returns a paginated list of question sets filtered by type
|
// @Description Returns a paginated list of question sets filtered by type
|
||||||
// @Tags question-sets
|
// @Tags question-sets
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param set_type query string true "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY, CAPSTONE)"
|
// @Param set_type query string true "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY)"
|
||||||
// @Param limit query int false "Limit" default(10)
|
// @Param limit query int false "Limit" default(10)
|
||||||
// @Param offset query int false "Offset" default(0)
|
// @Param offset query int false "Offset" default(0)
|
||||||
// @Success 200 {object} domain.Response
|
// @Success 200 {object} domain.Response
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,7 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Post("/course-management/sub-categories", a.authMiddleware, a.RequirePermission("course_categories.create"), h.CreateCourseSubCategory)
|
groupV1.Post("/course-management/sub-categories", a.authMiddleware, a.RequirePermission("course_categories.create"), h.CreateCourseSubCategory)
|
||||||
groupV1.Delete("/course-management/sub-categories/:subCategoryId", a.authMiddleware, a.RequirePermission("course_categories.delete"), h.DeleteCourseSubCategory)
|
groupV1.Delete("/course-management/sub-categories/:subCategoryId", a.authMiddleware, a.RequirePermission("course_categories.delete"), h.DeleteCourseSubCategory)
|
||||||
groupV1.Post("/course-management/levels", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateLevel)
|
groupV1.Post("/course-management/levels", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateLevel)
|
||||||
groupV1.Put("/course-management/levels/:levelId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateLevel)
|
|
||||||
groupV1.Post("/course-management/modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateModule)
|
groupV1.Post("/course-management/modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateModule)
|
||||||
groupV1.Put("/course-management/modules/:moduleId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateModule)
|
|
||||||
groupV1.Delete("/course-management/modules/:moduleId", a.authMiddleware, a.RequirePermission("subcourses.delete"), h.DeleteModule)
|
groupV1.Delete("/course-management/modules/:moduleId", a.authMiddleware, a.RequirePermission("subcourses.delete"), h.DeleteModule)
|
||||||
groupV1.Post("/course-management/sub-modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateSubModule)
|
groupV1.Post("/course-management/sub-modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateSubModule)
|
||||||
groupV1.Put("/course-management/sub-modules/:subModuleId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateSubModule)
|
groupV1.Put("/course-management/sub-modules/:subModuleId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateSubModule)
|
||||||
|
|
@ -118,25 +116,15 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Post("/course-management/sub-module-videos", a.authMiddleware, a.RequirePermission("videos.create"), h.CreateSubModuleVideo)
|
groupV1.Post("/course-management/sub-module-videos", a.authMiddleware, a.RequirePermission("videos.create"), h.CreateSubModuleVideo)
|
||||||
groupV1.Put("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.update"), h.UpdateSubModuleVideo)
|
groupV1.Put("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.update"), h.UpdateSubModuleVideo)
|
||||||
groupV1.Delete("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.delete"), h.DeleteSubModuleVideo)
|
groupV1.Delete("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.delete"), h.DeleteSubModuleVideo)
|
||||||
groupV1.Get("/course-management/sub-modules/:subModuleId/lessons", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.GetSubModuleLessons)
|
groupV1.Get("/course-management/sub-modules/:subModuleId/lessons", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetSubModuleLessons)
|
||||||
groupV1.Get("/course-management/sub-module-lessons/:lessonId", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.GetSubModuleLessonByID)
|
groupV1.Get("/course-management/sub-module-lessons/:lessonId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetSubModuleLessonByID)
|
||||||
groupV1.Put("/course-management/sub-module-lessons/:lessonId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateSubModuleLesson)
|
groupV1.Put("/course-management/sub-module-lessons/:lessonId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdateSubModuleLesson)
|
||||||
groupV1.Post("/course-management/sub-module-lessons", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateSubModuleLesson)
|
groupV1.Post("/course-management/sub-module-lessons", a.authMiddleware, a.RequirePermission("question_sets.update"), h.AttachSubModuleLesson)
|
||||||
groupV1.Get("/course-management/sub-modules/:subModuleId/practices", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetSubModulePractices)
|
groupV1.Get("/course-management/sub-modules/:subModuleId/practices", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetSubModulePractices)
|
||||||
groupV1.Get("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetSubModulePracticeByID)
|
groupV1.Get("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetSubModulePracticeByID)
|
||||||
groupV1.Post("/course-management/sub-module-practices", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateSubModulePractice)
|
groupV1.Post("/course-management/sub-module-practices", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateSubModulePractice)
|
||||||
groupV1.Put("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdatePractice)
|
groupV1.Put("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdatePractice)
|
||||||
groupV1.Delete("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeletePractice)
|
groupV1.Delete("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeletePractice)
|
||||||
groupV1.Get("/course-management/sub-modules/:subModuleId/capstones", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetSubModuleCapstones)
|
|
||||||
groupV1.Get("/course-management/capstones/:capstoneId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetSubModuleCapstoneByID)
|
|
||||||
groupV1.Post("/course-management/sub-module-capstones", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateSubModuleCapstone)
|
|
||||||
groupV1.Put("/course-management/capstones/:capstoneId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdateSubModuleCapstone)
|
|
||||||
groupV1.Delete("/course-management/capstones/:capstoneId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeleteCapstone)
|
|
||||||
groupV1.Get("/course-management/modules/:moduleId/capstones", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetModuleCapstones)
|
|
||||||
groupV1.Get("/course-management/module-capstones/:moduleCapstoneId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetModuleCapstoneByID)
|
|
||||||
groupV1.Post("/course-management/module-capstones", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateModuleCapstone)
|
|
||||||
groupV1.Put("/course-management/module-capstones/:moduleCapstoneId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdateModuleCapstone)
|
|
||||||
groupV1.Delete("/course-management/module-capstones/:moduleCapstoneId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeleteModuleCapstone)
|
|
||||||
|
|
||||||
// Questions
|
// Questions
|
||||||
groupV1.Post("/questions", a.authMiddleware, a.RequirePermission("questions.create"), h.CreateQuestion)
|
groupV1.Post("/questions", a.authMiddleware, a.RequirePermission("questions.create"), h.CreateQuestion)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user