free/premium feature
This commit is contained in:
parent
26cf7d2908
commit
c9a4bc1306
23
db/migrations/000081_content_access_tier.down.sql
Normal file
23
db/migrations/000081_content_access_tier.down.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
ALTER TABLE exam_prep.unit_module_lessons DROP CONSTRAINT IF EXISTS chk_exam_prep_unit_module_lessons_access_tier;
|
||||||
|
ALTER TABLE exam_prep.unit_module_lessons DROP COLUMN IF EXISTS access_tier;
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.unit_modules DROP CONSTRAINT IF EXISTS chk_exam_prep_unit_modules_access_tier;
|
||||||
|
ALTER TABLE exam_prep.unit_modules DROP COLUMN IF EXISTS access_tier;
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.units DROP CONSTRAINT IF EXISTS chk_exam_prep_units_access_tier;
|
||||||
|
ALTER TABLE exam_prep.units DROP COLUMN IF EXISTS access_tier;
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.catalog_courses DROP CONSTRAINT IF EXISTS chk_exam_prep_catalog_courses_access_tier;
|
||||||
|
ALTER TABLE exam_prep.catalog_courses DROP COLUMN IF EXISTS access_tier;
|
||||||
|
|
||||||
|
ALTER TABLE lessons DROP CONSTRAINT IF EXISTS chk_lessons_access_tier;
|
||||||
|
ALTER TABLE lessons DROP COLUMN IF EXISTS access_tier;
|
||||||
|
|
||||||
|
ALTER TABLE modules DROP CONSTRAINT IF EXISTS chk_modules_access_tier;
|
||||||
|
ALTER TABLE modules DROP COLUMN IF EXISTS access_tier;
|
||||||
|
|
||||||
|
ALTER TABLE courses DROP CONSTRAINT IF EXISTS chk_courses_access_tier;
|
||||||
|
ALTER TABLE courses DROP COLUMN IF EXISTS access_tier;
|
||||||
|
|
||||||
|
ALTER TABLE programs DROP CONSTRAINT IF EXISTS chk_programs_access_tier;
|
||||||
|
ALTER TABLE programs DROP COLUMN IF EXISTS access_tier;
|
||||||
34
db/migrations/000081_content_access_tier.up.sql
Normal file
34
db/migrations/000081_content_access_tier.up.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
-- FREE content is accessible without a subscription; PREMIUM requires one.
|
||||||
|
-- Effective tier cascades at read time: any PREMIUM ancestor makes descendants PREMIUM.
|
||||||
|
|
||||||
|
ALTER TABLE programs
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_programs_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
||||||
|
ALTER TABLE courses
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_courses_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
||||||
|
ALTER TABLE modules
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_modules_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
||||||
|
ALTER TABLE lessons
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_lessons_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.catalog_courses
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_exam_prep_catalog_courses_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.units
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_exam_prep_units_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.unit_modules
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_exam_prep_unit_modules_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.unit_module_lessons
|
||||||
|
ADD COLUMN access_tier VARCHAR(16) NOT NULL DEFAULT 'PREMIUM'
|
||||||
|
CONSTRAINT chk_exam_prep_unit_module_lessons_access_tier CHECK (access_tier IN ('FREE', 'PREMIUM'));
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: ExamPrepCreateCatalogCourse :one
|
-- name: ExamPrepCreateCatalogCourse :one
|
||||||
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order, publish_status)
|
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -9,7 +9,8 @@ SELECT
|
||||||
SELECT
|
SELECT
|
||||||
max(c.sort_order)
|
max(c.sort_order)
|
||||||
FROM exam_prep.catalog_courses AS c), 0) + 1,
|
FROM exam_prep.catalog_courses AS c), 0) + 1,
|
||||||
$5
|
$5,
|
||||||
|
$6
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -55,6 +56,7 @@ SELECT
|
||||||
c.thumbnail,
|
c.thumbnail,
|
||||||
c.sort_order,
|
c.sort_order,
|
||||||
c.publish_status,
|
c.publish_status,
|
||||||
|
c.access_tier,
|
||||||
COALESCE(cc.units_count, 0)::BIGINT AS units_count,
|
COALESCE(cc.units_count, 0)::BIGINT AS units_count,
|
||||||
COALESCE(cc.modules_count, 0)::BIGINT AS modules_count,
|
COALESCE(cc.modules_count, 0)::BIGINT AS modules_count,
|
||||||
COALESCE(cc.lessons_count, 0)::BIGINT AS lessons_count,
|
COALESCE(cc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||||
|
|
@ -96,6 +98,7 @@ SET
|
||||||
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = coalesce(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = sqlc.arg('id')
|
WHERE id = sqlc.arg('id')
|
||||||
RETURNING
|
RETURNING
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: ExamPrepCreateUnitModuleLesson :one
|
-- name: ExamPrepCreateUnitModuleLesson :one
|
||||||
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order, publish_status)
|
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -12,7 +12,8 @@ SELECT
|
||||||
FROM exam_prep.unit_module_lessons l
|
FROM exam_prep.unit_module_lessons l
|
||||||
WHERE
|
WHERE
|
||||||
l.unit_module_id = $1), 0) + 1,
|
l.unit_module_id = $1), 0) + 1,
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -59,6 +60,7 @@ SELECT
|
||||||
l.description,
|
l.description,
|
||||||
l.sort_order,
|
l.sort_order,
|
||||||
l.publish_status,
|
l.publish_status,
|
||||||
|
l.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
|
|
@ -89,6 +91,7 @@ SET
|
||||||
description = coalesce(sqlc.narg('description')::text, description),
|
description = coalesce(sqlc.narg('description')::text, description),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = coalesce(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = sqlc.arg('id')
|
WHERE id = sqlc.arg('id')
|
||||||
RETURNING
|
RETURNING
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: ExamPrepCreateUnitModule :one
|
-- name: ExamPrepCreateUnitModule :one
|
||||||
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order, publish_status)
|
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -12,7 +12,8 @@ SELECT
|
||||||
FROM exam_prep.unit_modules m
|
FROM exam_prep.unit_modules m
|
||||||
WHERE
|
WHERE
|
||||||
m.unit_id = $1), 0) + 1,
|
m.unit_id = $1), 0) + 1,
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -73,6 +74,7 @@ SELECT
|
||||||
m.icon,
|
m.icon,
|
||||||
m.sort_order,
|
m.sort_order,
|
||||||
m.publish_status,
|
m.publish_status,
|
||||||
|
m.access_tier,
|
||||||
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
|
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||||
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
|
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
|
||||||
(COALESCE(mc.practices_count, 0)::BIGINT > 0) AS has_practice,
|
(COALESCE(mc.practices_count, 0)::BIGINT > 0) AS has_practice,
|
||||||
|
|
@ -101,6 +103,7 @@ SET
|
||||||
icon = coalesce(sqlc.narg('icon')::text, icon),
|
icon = coalesce(sqlc.narg('icon')::text, icon),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = coalesce(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = sqlc.arg('id')
|
WHERE id = sqlc.arg('id')
|
||||||
RETURNING
|
RETURNING
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: ExamPrepCreateUnit :one
|
-- name: ExamPrepCreateUnit :one
|
||||||
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order, publish_status)
|
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.arg('catalog_course_id'),
|
sqlc.arg('catalog_course_id'),
|
||||||
sqlc.arg('name'),
|
sqlc.arg('name'),
|
||||||
|
|
@ -12,7 +12,8 @@ SELECT
|
||||||
FROM exam_prep.units u
|
FROM exam_prep.units u
|
||||||
WHERE
|
WHERE
|
||||||
u.catalog_course_id = sqlc.arg('catalog_course_id')), 0) + 1),
|
u.catalog_course_id = sqlc.arg('catalog_course_id')), 0) + 1),
|
||||||
sqlc.arg('publish_status')
|
sqlc.arg('publish_status'),
|
||||||
|
sqlc.arg('access_tier')
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -77,6 +78,7 @@ SELECT
|
||||||
u.thumbnail,
|
u.thumbnail,
|
||||||
u.sort_order,
|
u.sort_order,
|
||||||
u.publish_status,
|
u.publish_status,
|
||||||
|
u.access_tier,
|
||||||
COALESCE(uc.modules_count, 0)::BIGINT AS modules_count,
|
COALESCE(uc.modules_count, 0)::BIGINT AS modules_count,
|
||||||
COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count,
|
COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||||
COALESCE(uc.practices_count, 0)::BIGINT AS practices_count,
|
COALESCE(uc.practices_count, 0)::BIGINT AS practices_count,
|
||||||
|
|
@ -105,6 +107,7 @@ SET
|
||||||
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = coalesce(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = sqlc.arg('id')
|
WHERE id = sqlc.arg('id')
|
||||||
RETURNING
|
RETURNING
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: CreateCourse :one
|
-- name: CreateCourse :one
|
||||||
INSERT INTO courses (program_id, name, description, thumbnail, sort_order, publish_status)
|
INSERT INTO courses (program_id, name, description, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.arg('program_id'),
|
sqlc.arg('program_id'),
|
||||||
sqlc.arg('name'),
|
sqlc.arg('name'),
|
||||||
|
|
@ -12,7 +12,8 @@ SELECT
|
||||||
FROM courses c
|
FROM courses c
|
||||||
WHERE
|
WHERE
|
||||||
c.program_id = sqlc.arg('program_id')), 0) + 1),
|
c.program_id = sqlc.arg('program_id')), 0) + 1),
|
||||||
sqlc.arg('publish_status')
|
sqlc.arg('publish_status'),
|
||||||
|
sqlc.arg('access_tier')
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -63,6 +64,7 @@ SELECT
|
||||||
c.thumbnail,
|
c.thumbnail,
|
||||||
c.sort_order,
|
c.sort_order,
|
||||||
c.publish_status,
|
c.publish_status,
|
||||||
|
c.access_tier,
|
||||||
c.created_at,
|
c.created_at,
|
||||||
c.updated_at,
|
c.updated_at,
|
||||||
(
|
(
|
||||||
|
|
@ -123,6 +125,7 @@ SET
|
||||||
thumbnail = COALESCE(sqlc.narg('thumbnail')::text, thumbnail),
|
thumbnail = COALESCE(sqlc.narg('thumbnail')::text, thumbnail),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = COALESCE(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = sqlc.arg('id')
|
id = sqlc.arg('id')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: CreateLesson :one
|
-- name: CreateLesson :one
|
||||||
INSERT INTO lessons (module_id, title, video_url, thumbnail, description, sort_order, publish_status)
|
INSERT INTO lessons (module_id, title, video_url, thumbnail, description, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.arg('module_id'),
|
sqlc.arg('module_id'),
|
||||||
sqlc.arg('title'),
|
sqlc.arg('title'),
|
||||||
|
|
@ -13,7 +13,8 @@ SELECT
|
||||||
FROM lessons l
|
FROM lessons l
|
||||||
WHERE
|
WHERE
|
||||||
l.module_id = sqlc.arg('module_id')), 0) + 1),
|
l.module_id = sqlc.arg('module_id')), 0) + 1),
|
||||||
sqlc.arg('publish_status')
|
sqlc.arg('publish_status'),
|
||||||
|
sqlc.arg('access_tier')
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -51,6 +52,7 @@ SELECT
|
||||||
l.description,
|
l.description,
|
||||||
l.sort_order,
|
l.sort_order,
|
||||||
l.publish_status,
|
l.publish_status,
|
||||||
|
l.access_tier,
|
||||||
l.created_at,
|
l.created_at,
|
||||||
l.updated_at,
|
l.updated_at,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
|
|
@ -82,6 +84,7 @@ SET
|
||||||
description = COALESCE(sqlc.narg('description')::text, description),
|
description = COALESCE(sqlc.narg('description')::text, description),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = COALESCE(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = sqlc.arg('id')
|
id = sqlc.arg('id')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: CreateModule :one
|
-- name: CreateModule :one
|
||||||
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order, publish_status)
|
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.arg('program_id'),
|
sqlc.arg('program_id'),
|
||||||
sqlc.arg('course_id'),
|
sqlc.arg('course_id'),
|
||||||
|
|
@ -13,7 +13,8 @@ SELECT
|
||||||
FROM modules m
|
FROM modules m
|
||||||
WHERE
|
WHERE
|
||||||
m.course_id = sqlc.arg('course_id')), 0) + 1),
|
m.course_id = sqlc.arg('course_id')), 0) + 1),
|
||||||
sqlc.arg('publish_status')
|
sqlc.arg('publish_status'),
|
||||||
|
sqlc.arg('access_tier')
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -64,6 +65,7 @@ SELECT
|
||||||
m.icon,
|
m.icon,
|
||||||
m.sort_order,
|
m.sort_order,
|
||||||
m.publish_status,
|
m.publish_status,
|
||||||
|
m.access_tier,
|
||||||
m.created_at,
|
m.created_at,
|
||||||
m.updated_at,
|
m.updated_at,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
|
|
@ -96,6 +98,7 @@ SET
|
||||||
icon = COALESCE(sqlc.narg('icon')::text, icon),
|
icon = COALESCE(sqlc.narg('icon')::text, icon),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = COALESCE(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = sqlc.arg('id')
|
id = sqlc.arg('id')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- name: CreateProgram :one
|
-- name: CreateProgram :one
|
||||||
INSERT INTO programs (name, description, category, thumbnail, sort_order, publish_status)
|
INSERT INTO programs (name, description, category, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.arg('name'),
|
sqlc.arg('name'),
|
||||||
sqlc.arg('description'),
|
sqlc.arg('description'),
|
||||||
|
|
@ -9,7 +9,8 @@ SELECT
|
||||||
SELECT
|
SELECT
|
||||||
max(p.sort_order)
|
max(p.sort_order)
|
||||||
FROM programs AS p), 0) + 1),
|
FROM programs AS p), 0) + 1),
|
||||||
sqlc.arg('publish_status')
|
sqlc.arg('publish_status'),
|
||||||
|
sqlc.arg('access_tier')
|
||||||
RETURNING
|
RETURNING
|
||||||
*;
|
*;
|
||||||
|
|
||||||
|
|
@ -36,6 +37,7 @@ SELECT
|
||||||
p.thumbnail,
|
p.thumbnail,
|
||||||
p.sort_order,
|
p.sort_order,
|
||||||
p.publish_status,
|
p.publish_status,
|
||||||
|
p.access_tier,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM programs p
|
FROM programs p
|
||||||
|
|
@ -59,6 +61,7 @@ SET
|
||||||
thumbnail = COALESCE(sqlc.narg('thumbnail')::text, thumbnail),
|
thumbnail = COALESCE(sqlc.narg('thumbnail')::text, thumbnail),
|
||||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||||
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
|
access_tier = COALESCE(sqlc.narg('access_tier')::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = sqlc.arg('id')
|
id = sqlc.arg('id')
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const ExamPrepCreateCatalogCourse = `-- name: ExamPrepCreateCatalogCourse :one
|
const ExamPrepCreateCatalogCourse = `-- name: ExamPrepCreateCatalogCourse :one
|
||||||
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order, publish_status)
|
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -22,9 +22,10 @@ SELECT
|
||||||
SELECT
|
SELECT
|
||||||
max(c.sort_order)
|
max(c.sort_order)
|
||||||
FROM exam_prep.catalog_courses AS c), 0) + 1,
|
FROM exam_prep.catalog_courses AS c), 0) + 1,
|
||||||
$5
|
$5,
|
||||||
|
$6
|
||||||
RETURNING
|
RETURNING
|
||||||
id, name, description, thumbnail, sort_order, created_at, updated_at, category, publish_status
|
id, name, description, thumbnail, sort_order, created_at, updated_at, category, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepCreateCatalogCourseParams struct {
|
type ExamPrepCreateCatalogCourseParams struct {
|
||||||
|
|
@ -33,6 +34,7 @@ type ExamPrepCreateCatalogCourseParams struct {
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ExamPrepCreateCatalogCourse(ctx context.Context, arg ExamPrepCreateCatalogCourseParams) (ExamPrepCatalogCourse, error) {
|
func (q *Queries) ExamPrepCreateCatalogCourse(ctx context.Context, arg ExamPrepCreateCatalogCourseParams) (ExamPrepCatalogCourse, error) {
|
||||||
|
|
@ -42,6 +44,7 @@ func (q *Queries) ExamPrepCreateCatalogCourse(ctx context.Context, arg ExamPrepC
|
||||||
arg.Category,
|
arg.Category,
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i ExamPrepCatalogCourse
|
var i ExamPrepCatalogCourse
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -54,6 +57,7 @@ func (q *Queries) ExamPrepCreateCatalogCourse(ctx context.Context, arg ExamPrepC
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +74,7 @@ func (q *Queries) ExamPrepDeleteCatalogCourse(ctx context.Context, id int64) err
|
||||||
|
|
||||||
const ExamPrepGetCatalogCourseByID = `-- name: ExamPrepGetCatalogCourseByID :one
|
const ExamPrepGetCatalogCourseByID = `-- name: ExamPrepGetCatalogCourseByID :one
|
||||||
SELECT
|
SELECT
|
||||||
c.id, c.name, c.description, c.thumbnail, c.sort_order, c.created_at, c.updated_at, c.category, c.publish_status,
|
c.id, c.name, c.description, c.thumbnail, c.sort_order, c.created_at, c.updated_at, c.category, c.publish_status, c.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
|
|
@ -97,6 +101,7 @@ type ExamPrepGetCatalogCourseByIDRow struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +118,7 @@ func (q *Queries) ExamPrepGetCatalogCourseByID(ctx context.Context, id int64) (E
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -169,6 +175,7 @@ SELECT
|
||||||
c.thumbnail,
|
c.thumbnail,
|
||||||
c.sort_order,
|
c.sort_order,
|
||||||
c.publish_status,
|
c.publish_status,
|
||||||
|
c.access_tier,
|
||||||
COALESCE(cc.units_count, 0)::BIGINT AS units_count,
|
COALESCE(cc.units_count, 0)::BIGINT AS units_count,
|
||||||
COALESCE(cc.modules_count, 0)::BIGINT AS modules_count,
|
COALESCE(cc.modules_count, 0)::BIGINT AS modules_count,
|
||||||
COALESCE(cc.lessons_count, 0)::BIGINT AS lessons_count,
|
COALESCE(cc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||||
|
|
@ -211,6 +218,7 @@ type ExamPrepListCatalogCoursesRow struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
UnitsCount int64 `json:"units_count"`
|
UnitsCount int64 `json:"units_count"`
|
||||||
ModulesCount int64 `json:"modules_count"`
|
ModulesCount int64 `json:"modules_count"`
|
||||||
LessonsCount int64 `json:"lessons_count"`
|
LessonsCount int64 `json:"lessons_count"`
|
||||||
|
|
@ -237,6 +245,7 @@ func (q *Queries) ExamPrepListCatalogCourses(ctx context.Context, arg ExamPrepLi
|
||||||
&i.Thumbnail,
|
&i.Thumbnail,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.UnitsCount,
|
&i.UnitsCount,
|
||||||
&i.ModulesCount,
|
&i.ModulesCount,
|
||||||
&i.LessonsCount,
|
&i.LessonsCount,
|
||||||
|
|
@ -263,10 +272,11 @@ SET
|
||||||
thumbnail = coalesce($4::text, thumbnail),
|
thumbnail = coalesce($4::text, thumbnail),
|
||||||
sort_order = coalesce($5::int, sort_order),
|
sort_order = coalesce($5::int, sort_order),
|
||||||
publish_status = coalesce($6::varchar, publish_status),
|
publish_status = coalesce($6::varchar, publish_status),
|
||||||
|
access_tier = coalesce($7::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $7
|
WHERE id = $8
|
||||||
RETURNING
|
RETURNING
|
||||||
id, name, description, thumbnail, sort_order, created_at, updated_at, category, publish_status
|
id, name, description, thumbnail, sort_order, created_at, updated_at, category, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepUpdateCatalogCourseParams struct {
|
type ExamPrepUpdateCatalogCourseParams struct {
|
||||||
|
|
@ -276,6 +286,7 @@ type ExamPrepUpdateCatalogCourseParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,6 +298,7 @@ func (q *Queries) ExamPrepUpdateCatalogCourse(ctx context.Context, arg ExamPrepU
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i ExamPrepCatalogCourse
|
var i ExamPrepCatalogCourse
|
||||||
|
|
@ -300,6 +312,7 @@ func (q *Queries) ExamPrepUpdateCatalogCourse(ctx context.Context, arg ExamPrepU
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const ExamPrepCreateUnitModuleLesson = `-- name: ExamPrepCreateUnitModuleLesson :one
|
const ExamPrepCreateUnitModuleLesson = `-- name: ExamPrepCreateUnitModuleLesson :one
|
||||||
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order, publish_status)
|
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -25,9 +25,10 @@ SELECT
|
||||||
FROM exam_prep.unit_module_lessons l
|
FROM exam_prep.unit_module_lessons l
|
||||||
WHERE
|
WHERE
|
||||||
l.unit_module_id = $1), 0) + 1,
|
l.unit_module_id = $1), 0) + 1,
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at, publish_status
|
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepCreateUnitModuleLessonParams struct {
|
type ExamPrepCreateUnitModuleLessonParams struct {
|
||||||
|
|
@ -37,6 +38,7 @@ type ExamPrepCreateUnitModuleLessonParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ExamPrepCreateUnitModuleLesson(ctx context.Context, arg ExamPrepCreateUnitModuleLessonParams) (ExamPrepUnitModuleLesson, error) {
|
func (q *Queries) ExamPrepCreateUnitModuleLesson(ctx context.Context, arg ExamPrepCreateUnitModuleLessonParams) (ExamPrepUnitModuleLesson, error) {
|
||||||
|
|
@ -47,6 +49,7 @@ func (q *Queries) ExamPrepCreateUnitModuleLesson(ctx context.Context, arg ExamPr
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i ExamPrepUnitModuleLesson
|
var i ExamPrepUnitModuleLesson
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -60,6 +63,7 @@ func (q *Queries) ExamPrepCreateUnitModuleLesson(ctx context.Context, arg ExamPr
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +80,7 @@ func (q *Queries) ExamPrepDeleteUnitModuleLesson(ctx context.Context, id int64)
|
||||||
|
|
||||||
const ExamPrepGetUnitModuleLessonByID = `-- name: ExamPrepGetUnitModuleLessonByID :one
|
const ExamPrepGetUnitModuleLessonByID = `-- name: ExamPrepGetUnitModuleLessonByID :one
|
||||||
SELECT
|
SELECT
|
||||||
l.id, l.unit_module_id, l.title, l.video_url, l.thumbnail, l.description, l.sort_order, l.created_at, l.updated_at, l.publish_status,
|
l.id, l.unit_module_id, l.title, l.video_url, l.thumbnail, l.description, l.sort_order, l.created_at, l.updated_at, l.publish_status, l.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
|
|
@ -98,6 +102,7 @@ type ExamPrepGetUnitModuleLessonByIDRow struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,6 +120,7 @@ func (q *Queries) ExamPrepGetUnitModuleLessonByID(ctx context.Context, id int64)
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -193,6 +199,7 @@ SELECT
|
||||||
l.description,
|
l.description,
|
||||||
l.sort_order,
|
l.sort_order,
|
||||||
l.publish_status,
|
l.publish_status,
|
||||||
|
l.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
|
|
@ -232,6 +239,7 @@ type ExamPrepListUnitModuleLessonsByUnitModuleIDRow struct {
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
|
@ -261,6 +269,7 @@ func (q *Queries) ExamPrepListUnitModuleLessonsByUnitModuleID(ctx context.Contex
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
|
@ -284,10 +293,11 @@ SET
|
||||||
description = coalesce($4::text, description),
|
description = coalesce($4::text, description),
|
||||||
sort_order = coalesce($5::int, sort_order),
|
sort_order = coalesce($5::int, sort_order),
|
||||||
publish_status = coalesce($6::varchar, publish_status),
|
publish_status = coalesce($6::varchar, publish_status),
|
||||||
|
access_tier = coalesce($7::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $7
|
WHERE id = $8
|
||||||
RETURNING
|
RETURNING
|
||||||
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at, publish_status
|
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepUpdateUnitModuleLessonParams struct {
|
type ExamPrepUpdateUnitModuleLessonParams struct {
|
||||||
|
|
@ -297,6 +307,7 @@ type ExamPrepUpdateUnitModuleLessonParams struct {
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,6 +319,7 @@ func (q *Queries) ExamPrepUpdateUnitModuleLesson(ctx context.Context, arg ExamPr
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i ExamPrepUnitModuleLesson
|
var i ExamPrepUnitModuleLesson
|
||||||
|
|
@ -322,6 +334,7 @@ func (q *Queries) ExamPrepUpdateUnitModuleLesson(ctx context.Context, arg ExamPr
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const ExamPrepCreateUnitModule = `-- name: ExamPrepCreateUnitModule :one
|
const ExamPrepCreateUnitModule = `-- name: ExamPrepCreateUnitModule :one
|
||||||
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order, publish_status)
|
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -25,9 +25,10 @@ SELECT
|
||||||
FROM exam_prep.unit_modules m
|
FROM exam_prep.unit_modules m
|
||||||
WHERE
|
WHERE
|
||||||
m.unit_id = $1), 0) + 1,
|
m.unit_id = $1), 0) + 1,
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at, publish_status
|
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepCreateUnitModuleParams struct {
|
type ExamPrepCreateUnitModuleParams struct {
|
||||||
|
|
@ -37,6 +38,7 @@ type ExamPrepCreateUnitModuleParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
Icon pgtype.Text `json:"icon"`
|
Icon pgtype.Text `json:"icon"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ExamPrepCreateUnitModule(ctx context.Context, arg ExamPrepCreateUnitModuleParams) (ExamPrepUnitModule, error) {
|
func (q *Queries) ExamPrepCreateUnitModule(ctx context.Context, arg ExamPrepCreateUnitModuleParams) (ExamPrepUnitModule, error) {
|
||||||
|
|
@ -47,6 +49,7 @@ func (q *Queries) ExamPrepCreateUnitModule(ctx context.Context, arg ExamPrepCrea
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.Icon,
|
arg.Icon,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i ExamPrepUnitModule
|
var i ExamPrepUnitModule
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -60,6 +63,7 @@ func (q *Queries) ExamPrepCreateUnitModule(ctx context.Context, arg ExamPrepCrea
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +80,7 @@ func (q *Queries) ExamPrepDeleteUnitModule(ctx context.Context, id int64) error
|
||||||
|
|
||||||
const ExamPrepGetUnitModuleByID = `-- name: ExamPrepGetUnitModuleByID :one
|
const ExamPrepGetUnitModuleByID = `-- name: ExamPrepGetUnitModuleByID :one
|
||||||
SELECT
|
SELECT
|
||||||
m.id, m.unit_id, m.name, m.description, m.thumbnail, m.icon, m.sort_order, m.created_at, m.updated_at, m.publish_status,
|
m.id, m.unit_id, m.name, m.description, m.thumbnail, m.icon, m.sort_order, m.created_at, m.updated_at, m.publish_status, m.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
|
|
@ -100,6 +104,7 @@ type ExamPrepGetUnitModuleByIDRow struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,6 +122,7 @@ func (q *Queries) ExamPrepGetUnitModuleByID(ctx context.Context, id int64) (Exam
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -207,6 +213,7 @@ SELECT
|
||||||
m.icon,
|
m.icon,
|
||||||
m.sort_order,
|
m.sort_order,
|
||||||
m.publish_status,
|
m.publish_status,
|
||||||
|
m.access_tier,
|
||||||
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
|
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||||
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
|
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
|
||||||
(COALESCE(mc.practices_count, 0)::BIGINT > 0) AS has_practice,
|
(COALESCE(mc.practices_count, 0)::BIGINT > 0) AS has_practice,
|
||||||
|
|
@ -244,6 +251,7 @@ type ExamPrepListUnitModulesByUnitRow struct {
|
||||||
Icon pgtype.Text `json:"icon"`
|
Icon pgtype.Text `json:"icon"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
LessonsCount int64 `json:"lessons_count"`
|
LessonsCount int64 `json:"lessons_count"`
|
||||||
PracticesCount int64 `json:"practices_count"`
|
PracticesCount int64 `json:"practices_count"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
|
|
@ -275,6 +283,7 @@ func (q *Queries) ExamPrepListUnitModulesByUnit(ctx context.Context, arg ExamPre
|
||||||
&i.Icon,
|
&i.Icon,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.LessonsCount,
|
&i.LessonsCount,
|
||||||
&i.PracticesCount,
|
&i.PracticesCount,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
|
|
@ -300,10 +309,11 @@ SET
|
||||||
icon = coalesce($4::text, icon),
|
icon = coalesce($4::text, icon),
|
||||||
sort_order = coalesce($5::int, sort_order),
|
sort_order = coalesce($5::int, sort_order),
|
||||||
publish_status = coalesce($6::varchar, publish_status),
|
publish_status = coalesce($6::varchar, publish_status),
|
||||||
|
access_tier = coalesce($7::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $7
|
WHERE id = $8
|
||||||
RETURNING
|
RETURNING
|
||||||
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at, publish_status
|
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepUpdateUnitModuleParams struct {
|
type ExamPrepUpdateUnitModuleParams struct {
|
||||||
|
|
@ -313,6 +323,7 @@ type ExamPrepUpdateUnitModuleParams struct {
|
||||||
Icon pgtype.Text `json:"icon"`
|
Icon pgtype.Text `json:"icon"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,6 +335,7 @@ func (q *Queries) ExamPrepUpdateUnitModule(ctx context.Context, arg ExamPrepUpda
|
||||||
arg.Icon,
|
arg.Icon,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i ExamPrepUnitModule
|
var i ExamPrepUnitModule
|
||||||
|
|
@ -338,6 +350,7 @@ func (q *Queries) ExamPrepUpdateUnitModule(ctx context.Context, arg ExamPrepUpda
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const ExamPrepCreateUnit = `-- name: ExamPrepCreateUnit :one
|
const ExamPrepCreateUnit = `-- name: ExamPrepCreateUnit :one
|
||||||
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order, publish_status)
|
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -25,9 +25,10 @@ SELECT
|
||||||
FROM exam_prep.units u
|
FROM exam_prep.units u
|
||||||
WHERE
|
WHERE
|
||||||
u.catalog_course_id = $1), 0) + 1),
|
u.catalog_course_id = $1), 0) + 1),
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at, publish_status
|
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepCreateUnitParams struct {
|
type ExamPrepCreateUnitParams struct {
|
||||||
|
|
@ -37,6 +38,7 @@ type ExamPrepCreateUnitParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ExamPrepCreateUnit(ctx context.Context, arg ExamPrepCreateUnitParams) (ExamPrepUnit, error) {
|
func (q *Queries) ExamPrepCreateUnit(ctx context.Context, arg ExamPrepCreateUnitParams) (ExamPrepUnit, error) {
|
||||||
|
|
@ -47,6 +49,7 @@ func (q *Queries) ExamPrepCreateUnit(ctx context.Context, arg ExamPrepCreateUnit
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i ExamPrepUnit
|
var i ExamPrepUnit
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -59,6 +62,7 @@ func (q *Queries) ExamPrepCreateUnit(ctx context.Context, arg ExamPrepCreateUnit
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +79,7 @@ func (q *Queries) ExamPrepDeleteUnit(ctx context.Context, id int64) error {
|
||||||
|
|
||||||
const ExamPrepGetUnitByID = `-- name: ExamPrepGetUnitByID :one
|
const ExamPrepGetUnitByID = `-- name: ExamPrepGetUnitByID :one
|
||||||
SELECT
|
SELECT
|
||||||
u.id, u.catalog_course_id, u.name, u.description, u.thumbnail, u.sort_order, u.created_at, u.updated_at, u.publish_status,
|
u.id, u.catalog_course_id, u.name, u.description, u.thumbnail, u.sort_order, u.created_at, u.updated_at, u.publish_status, u.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
|
|
@ -100,6 +104,7 @@ type ExamPrepGetUnitByIDRow struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,6 +121,7 @@ func (q *Queries) ExamPrepGetUnitByID(ctx context.Context, id int64) (ExamPrepGe
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -208,6 +214,7 @@ SELECT
|
||||||
u.thumbnail,
|
u.thumbnail,
|
||||||
u.sort_order,
|
u.sort_order,
|
||||||
u.publish_status,
|
u.publish_status,
|
||||||
|
u.access_tier,
|
||||||
COALESCE(uc.modules_count, 0)::BIGINT AS modules_count,
|
COALESCE(uc.modules_count, 0)::BIGINT AS modules_count,
|
||||||
COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count,
|
COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||||
COALESCE(uc.practices_count, 0)::BIGINT AS practices_count,
|
COALESCE(uc.practices_count, 0)::BIGINT AS practices_count,
|
||||||
|
|
@ -245,6 +252,7 @@ type ExamPrepListUnitsByCatalogCourseRow struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
ModulesCount int64 `json:"modules_count"`
|
ModulesCount int64 `json:"modules_count"`
|
||||||
LessonsCount int64 `json:"lessons_count"`
|
LessonsCount int64 `json:"lessons_count"`
|
||||||
PracticesCount int64 `json:"practices_count"`
|
PracticesCount int64 `json:"practices_count"`
|
||||||
|
|
@ -276,6 +284,7 @@ func (q *Queries) ExamPrepListUnitsByCatalogCourse(ctx context.Context, arg Exam
|
||||||
&i.Thumbnail,
|
&i.Thumbnail,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.ModulesCount,
|
&i.ModulesCount,
|
||||||
&i.LessonsCount,
|
&i.LessonsCount,
|
||||||
&i.PracticesCount,
|
&i.PracticesCount,
|
||||||
|
|
@ -301,10 +310,11 @@ SET
|
||||||
thumbnail = coalesce($3::text, thumbnail),
|
thumbnail = coalesce($3::text, thumbnail),
|
||||||
sort_order = coalesce($4::int, sort_order),
|
sort_order = coalesce($4::int, sort_order),
|
||||||
publish_status = coalesce($5::varchar, publish_status),
|
publish_status = coalesce($5::varchar, publish_status),
|
||||||
|
access_tier = coalesce($6::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $6
|
WHERE id = $7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at, publish_status
|
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepUpdateUnitParams struct {
|
type ExamPrepUpdateUnitParams struct {
|
||||||
|
|
@ -313,6 +323,7 @@ type ExamPrepUpdateUnitParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,6 +334,7 @@ func (q *Queries) ExamPrepUpdateUnit(ctx context.Context, arg ExamPrepUpdateUnit
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i ExamPrepUnit
|
var i ExamPrepUnit
|
||||||
|
|
@ -336,6 +348,7 @@ func (q *Queries) ExamPrepUpdateUnit(ctx context.Context, arg ExamPrepUpdateUnit
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const CreateCourse = `-- name: CreateCourse :one
|
const CreateCourse = `-- name: CreateCourse :one
|
||||||
INSERT INTO courses (program_id, name, description, thumbnail, sort_order, publish_status)
|
INSERT INTO courses (program_id, name, description, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -25,9 +25,10 @@ SELECT
|
||||||
FROM courses c
|
FROM courses c
|
||||||
WHERE
|
WHERE
|
||||||
c.program_id = $1), 0) + 1),
|
c.program_id = $1), 0) + 1),
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order, publish_status
|
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateCourseParams struct {
|
type CreateCourseParams struct {
|
||||||
|
|
@ -37,6 +38,7 @@ type CreateCourseParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Course, error) {
|
func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Course, error) {
|
||||||
|
|
@ -47,6 +49,7 @@ func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Cou
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i Course
|
var i Course
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -59,6 +62,7 @@ func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Cou
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +79,7 @@ func (q *Queries) DeleteCourse(ctx context.Context, id int64) error {
|
||||||
|
|
||||||
const GetCourseByID = `-- name: GetCourseByID :one
|
const GetCourseByID = `-- name: GetCourseByID :one
|
||||||
SELECT
|
SELECT
|
||||||
c.id, c.program_id, c.name, c.description, c.thumbnail, c.created_at, c.updated_at, c.sort_order, c.publish_status,
|
c.id, c.program_id, c.name, c.description, c.thumbnail, c.created_at, c.updated_at, c.sort_order, c.publish_status, c.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
|
|
@ -99,6 +103,7 @@ type GetCourseByIDRow struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,6 +120,7 @@ func (q *Queries) GetCourseByID(ctx context.Context, id int64) (GetCourseByIDRow
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -161,6 +167,7 @@ SELECT
|
||||||
c.thumbnail,
|
c.thumbnail,
|
||||||
c.sort_order,
|
c.sort_order,
|
||||||
c.publish_status,
|
c.publish_status,
|
||||||
|
c.access_tier,
|
||||||
c.created_at,
|
c.created_at,
|
||||||
c.updated_at,
|
c.updated_at,
|
||||||
(
|
(
|
||||||
|
|
@ -230,6 +237,7 @@ type ListCoursesByProgramIDRow struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
ModuleCount int64 `json:"module_count"`
|
ModuleCount int64 `json:"module_count"`
|
||||||
|
|
@ -261,6 +269,7 @@ func (q *Queries) ListCoursesByProgramID(ctx context.Context, arg ListCoursesByP
|
||||||
&i.Thumbnail,
|
&i.Thumbnail,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.ModuleCount,
|
&i.ModuleCount,
|
||||||
|
|
@ -319,11 +328,12 @@ SET
|
||||||
thumbnail = COALESCE($3::text, thumbnail),
|
thumbnail = COALESCE($3::text, thumbnail),
|
||||||
sort_order = coalesce($4::int, sort_order),
|
sort_order = coalesce($4::int, sort_order),
|
||||||
publish_status = COALESCE($5::varchar, publish_status),
|
publish_status = COALESCE($5::varchar, publish_status),
|
||||||
|
access_tier = COALESCE($6::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = $6
|
id = $7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order, publish_status
|
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateCourseParams struct {
|
type UpdateCourseParams struct {
|
||||||
|
|
@ -332,6 +342,7 @@ type UpdateCourseParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,6 +353,7 @@ func (q *Queries) UpdateCourse(ctx context.Context, arg UpdateCourseParams) (Cou
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i Course
|
var i Course
|
||||||
|
|
@ -355,6 +367,7 @@ func (q *Queries) UpdateCourse(ctx context.Context, arg UpdateCourseParams) (Cou
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const CreateLesson = `-- name: CreateLesson :one
|
const CreateLesson = `-- name: CreateLesson :one
|
||||||
INSERT INTO lessons (module_id, title, video_url, thumbnail, description, sort_order, publish_status)
|
INSERT INTO lessons (module_id, title, video_url, thumbnail, description, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -26,9 +26,10 @@ SELECT
|
||||||
FROM lessons l
|
FROM lessons l
|
||||||
WHERE
|
WHERE
|
||||||
l.module_id = $1), 0) + 1),
|
l.module_id = $1), 0) + 1),
|
||||||
$7
|
$7,
|
||||||
|
$8
|
||||||
RETURNING
|
RETURNING
|
||||||
id, module_id, title, video_url, thumbnail, description, created_at, updated_at, sort_order, publish_status
|
id, module_id, title, video_url, thumbnail, description, created_at, updated_at, sort_order, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateLessonParams struct {
|
type CreateLessonParams struct {
|
||||||
|
|
@ -39,6 +40,7 @@ type CreateLessonParams struct {
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateLesson(ctx context.Context, arg CreateLessonParams) (Lesson, error) {
|
func (q *Queries) CreateLesson(ctx context.Context, arg CreateLessonParams) (Lesson, error) {
|
||||||
|
|
@ -50,6 +52,7 @@ func (q *Queries) CreateLesson(ctx context.Context, arg CreateLessonParams) (Les
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i Lesson
|
var i Lesson
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -63,6 +66,7 @@ func (q *Queries) CreateLesson(ctx context.Context, arg CreateLessonParams) (Les
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +83,7 @@ func (q *Queries) DeleteLesson(ctx context.Context, id int64) error {
|
||||||
|
|
||||||
const GetLessonByID = `-- name: GetLessonByID :one
|
const GetLessonByID = `-- name: GetLessonByID :one
|
||||||
SELECT
|
SELECT
|
||||||
l.id, l.module_id, l.title, l.video_url, l.thumbnail, l.description, l.created_at, l.updated_at, l.sort_order, l.publish_status,
|
l.id, l.module_id, l.title, l.video_url, l.thumbnail, l.description, l.created_at, l.updated_at, l.sort_order, l.publish_status, l.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
|
|
@ -102,6 +106,7 @@ type GetLessonByIDRow struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,6 +124,7 @@ func (q *Queries) GetLessonByID(ctx context.Context, id int64) (GetLessonByIDRow
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -166,6 +172,7 @@ SELECT
|
||||||
l.description,
|
l.description,
|
||||||
l.sort_order,
|
l.sort_order,
|
||||||
l.publish_status,
|
l.publish_status,
|
||||||
|
l.access_tier,
|
||||||
l.created_at,
|
l.created_at,
|
||||||
l.updated_at,
|
l.updated_at,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
|
|
@ -206,6 +213,7 @@ type ListLessonsByModuleIDRow struct {
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
|
|
@ -235,6 +243,7 @@ func (q *Queries) ListLessonsByModuleID(ctx context.Context, arg ListLessonsByMo
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
|
|
@ -258,11 +267,12 @@ SET
|
||||||
description = COALESCE($4::text, description),
|
description = COALESCE($4::text, description),
|
||||||
sort_order = coalesce($5::int, sort_order),
|
sort_order = coalesce($5::int, sort_order),
|
||||||
publish_status = COALESCE($6::varchar, publish_status),
|
publish_status = COALESCE($6::varchar, publish_status),
|
||||||
|
access_tier = COALESCE($7::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = $7
|
id = $8
|
||||||
RETURNING
|
RETURNING
|
||||||
id, module_id, title, video_url, thumbnail, description, created_at, updated_at, sort_order, publish_status
|
id, module_id, title, video_url, thumbnail, description, created_at, updated_at, sort_order, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateLessonParams struct {
|
type UpdateLessonParams struct {
|
||||||
|
|
@ -272,6 +282,7 @@ type UpdateLessonParams struct {
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,6 +294,7 @@ func (q *Queries) UpdateLesson(ctx context.Context, arg UpdateLessonParams) (Les
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i Lesson
|
var i Lesson
|
||||||
|
|
@ -297,6 +309,7 @@ func (q *Queries) UpdateLesson(ctx context.Context, arg UpdateLessonParams) (Les
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const CreateModule = `-- name: CreateModule :one
|
const CreateModule = `-- name: CreateModule :one
|
||||||
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order, publish_status)
|
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -26,9 +26,10 @@ SELECT
|
||||||
FROM modules m
|
FROM modules m
|
||||||
WHERE
|
WHERE
|
||||||
m.course_id = $2), 0) + 1),
|
m.course_id = $2), 0) + 1),
|
||||||
$7
|
$7,
|
||||||
|
$8
|
||||||
RETURNING
|
RETURNING
|
||||||
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order, publish_status
|
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateModuleParams struct {
|
type CreateModuleParams struct {
|
||||||
|
|
@ -39,6 +40,7 @@ type CreateModuleParams struct {
|
||||||
Icon pgtype.Text `json:"icon"`
|
Icon pgtype.Text `json:"icon"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) {
|
func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) {
|
||||||
|
|
@ -50,6 +52,7 @@ func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Mod
|
||||||
arg.Icon,
|
arg.Icon,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i Module
|
var i Module
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -63,6 +66,7 @@ func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Mod
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +83,7 @@ func (q *Queries) DeleteModule(ctx context.Context, id int64) error {
|
||||||
|
|
||||||
const GetModuleByID = `-- name: GetModuleByID :one
|
const GetModuleByID = `-- name: GetModuleByID :one
|
||||||
SELECT
|
SELECT
|
||||||
m.id, m.program_id, m.course_id, m.name, m.description, m.icon, m.created_at, m.updated_at, m.sort_order, m.publish_status,
|
m.id, m.program_id, m.course_id, m.name, m.description, m.icon, m.created_at, m.updated_at, m.sort_order, m.publish_status, m.access_tier,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
|
|
@ -103,6 +107,7 @@ type GetModuleByIDRow struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,6 +125,7 @@ func (q *Queries) GetModuleByID(ctx context.Context, id int64) (GetModuleByIDRow
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -167,6 +173,7 @@ SELECT
|
||||||
m.icon,
|
m.icon,
|
||||||
m.sort_order,
|
m.sort_order,
|
||||||
m.publish_status,
|
m.publish_status,
|
||||||
|
m.access_tier,
|
||||||
m.created_at,
|
m.created_at,
|
||||||
m.updated_at,
|
m.updated_at,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
|
|
@ -210,6 +217,7 @@ type ListModulesByProgramAndCourseRow struct {
|
||||||
Icon pgtype.Text `json:"icon"`
|
Icon pgtype.Text `json:"icon"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
|
|
@ -240,6 +248,7 @@ func (q *Queries) ListModulesByProgramAndCourse(ctx context.Context, arg ListMod
|
||||||
&i.Icon,
|
&i.Icon,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.HasPractice,
|
&i.HasPractice,
|
||||||
|
|
@ -295,11 +304,12 @@ SET
|
||||||
icon = COALESCE($3::text, icon),
|
icon = COALESCE($3::text, icon),
|
||||||
sort_order = coalesce($4::int, sort_order),
|
sort_order = coalesce($4::int, sort_order),
|
||||||
publish_status = COALESCE($5::varchar, publish_status),
|
publish_status = COALESCE($5::varchar, publish_status),
|
||||||
|
access_tier = COALESCE($6::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = $6
|
id = $7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order, publish_status
|
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateModuleParams struct {
|
type UpdateModuleParams struct {
|
||||||
|
|
@ -308,6 +318,7 @@ type UpdateModuleParams struct {
|
||||||
Icon pgtype.Text `json:"icon"`
|
Icon pgtype.Text `json:"icon"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,6 +329,7 @@ func (q *Queries) UpdateModule(ctx context.Context, arg UpdateModuleParams) (Mod
|
||||||
arg.Icon,
|
arg.Icon,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i Module
|
var i Module
|
||||||
|
|
@ -332,6 +344,7 @@ func (q *Queries) UpdateModule(ctx context.Context, arg UpdateModuleParams) (Mod
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -667,7 +667,7 @@ func (q *Queries) GetPracticeScopeByQuestionSetID(ctx context.Context, questionS
|
||||||
|
|
||||||
const GetPreviousCourseInProgram = `-- name: GetPreviousCourseInProgram :one
|
const GetPreviousCourseInProgram = `-- name: GetPreviousCourseInProgram :one
|
||||||
SELECT
|
SELECT
|
||||||
c2.id, c2.program_id, c2.name, c2.description, c2.thumbnail, c2.created_at, c2.updated_at, c2.sort_order, c2.publish_status
|
c2.id, c2.program_id, c2.name, c2.description, c2.thumbnail, c2.created_at, c2.updated_at, c2.sort_order, c2.publish_status, c2.access_tier
|
||||||
FROM
|
FROM
|
||||||
courses AS c1
|
courses AS c1
|
||||||
INNER JOIN courses AS c2 ON c2.program_id = c1.program_id
|
INNER JOIN courses AS c2 ON c2.program_id = c1.program_id
|
||||||
|
|
@ -700,13 +700,14 @@ func (q *Queries) GetPreviousCourseInProgram(ctx context.Context, id int64) (Cou
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetPreviousLessonInModule = `-- name: GetPreviousLessonInModule :one
|
const GetPreviousLessonInModule = `-- name: GetPreviousLessonInModule :one
|
||||||
SELECT
|
SELECT
|
||||||
l2.id, l2.module_id, l2.title, l2.video_url, l2.thumbnail, l2.description, l2.created_at, l2.updated_at, l2.sort_order, l2.publish_status
|
l2.id, l2.module_id, l2.title, l2.video_url, l2.thumbnail, l2.description, l2.created_at, l2.updated_at, l2.sort_order, l2.publish_status, l2.access_tier
|
||||||
FROM
|
FROM
|
||||||
lessons AS l1
|
lessons AS l1
|
||||||
INNER JOIN lessons AS l2 ON l2.module_id = l1.module_id
|
INNER JOIN lessons AS l2 ON l2.module_id = l1.module_id
|
||||||
|
|
@ -741,13 +742,14 @@ func (q *Queries) GetPreviousLessonInModule(ctx context.Context, id int64) (Less
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetPreviousModuleInCourse = `-- name: GetPreviousModuleInCourse :one
|
const GetPreviousModuleInCourse = `-- name: GetPreviousModuleInCourse :one
|
||||||
SELECT
|
SELECT
|
||||||
m2.id, m2.program_id, m2.course_id, m2.name, m2.description, m2.icon, m2.created_at, m2.updated_at, m2.sort_order, m2.publish_status
|
m2.id, m2.program_id, m2.course_id, m2.name, m2.description, m2.icon, m2.created_at, m2.updated_at, m2.sort_order, m2.publish_status, m2.access_tier
|
||||||
FROM
|
FROM
|
||||||
modules AS m1
|
modules AS m1
|
||||||
INNER JOIN modules AS m2 ON m2.course_id = m1.course_id
|
INNER JOIN modules AS m2 ON m2.course_id = m1.course_id
|
||||||
|
|
@ -781,13 +783,14 @@ func (q *Queries) GetPreviousModuleInCourse(ctx context.Context, id int64) (Modu
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetPreviousProgram = `-- name: GetPreviousProgram :one
|
const GetPreviousProgram = `-- name: GetPreviousProgram :one
|
||||||
SELECT
|
SELECT
|
||||||
p2.id, p2.name, p2.description, p2.thumbnail, p2.created_at, p2.updated_at, p2.sort_order, p2.category, p2.publish_status
|
p2.id, p2.name, p2.description, p2.thumbnail, p2.created_at, p2.updated_at, p2.sort_order, p2.category, p2.publish_status, p2.access_tier
|
||||||
FROM
|
FROM
|
||||||
programs AS p1
|
programs AS p1
|
||||||
INNER JOIN programs AS p2 ON p2.category = p1.category
|
INNER JOIN programs AS p2 ON p2.category = p1.category
|
||||||
|
|
@ -821,6 +824,7 @@ func (q *Queries) GetPreviousProgram(ctx context.Context, id int64) (Program, er
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ type Course struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
|
|
@ -68,6 +69,7 @@ type ExamPrepCatalogCourse struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExamPrepLessonPractice struct {
|
type ExamPrepLessonPractice struct {
|
||||||
|
|
@ -94,6 +96,7 @@ type ExamPrepUnit struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExamPrepUnitModule struct {
|
type ExamPrepUnitModule struct {
|
||||||
|
|
@ -107,6 +110,7 @@ type ExamPrepUnitModule struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExamPrepUnitModuleLesson struct {
|
type ExamPrepUnitModuleLesson struct {
|
||||||
|
|
@ -120,6 +124,7 @@ type ExamPrepUnitModuleLesson struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Faq struct {
|
type Faq struct {
|
||||||
|
|
@ -162,6 +167,7 @@ type Lesson struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LevelToSubCourse struct {
|
type LevelToSubCourse struct {
|
||||||
|
|
@ -245,6 +251,7 @@ type Module struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModuleToSubCourse struct {
|
type ModuleToSubCourse struct {
|
||||||
|
|
@ -318,6 +325,7 @@ type Program struct {
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Question struct {
|
type Question struct {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const CreateProgram = `-- name: CreateProgram :one
|
const CreateProgram = `-- name: CreateProgram :one
|
||||||
INSERT INTO programs (name, description, category, thumbnail, sort_order, publish_status)
|
INSERT INTO programs (name, description, category, thumbnail, sort_order, publish_status, access_tier)
|
||||||
SELECT
|
SELECT
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
|
@ -22,9 +22,10 @@ SELECT
|
||||||
SELECT
|
SELECT
|
||||||
max(p.sort_order)
|
max(p.sort_order)
|
||||||
FROM programs AS p), 0) + 1),
|
FROM programs AS p), 0) + 1),
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
RETURNING
|
RETURNING
|
||||||
id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status
|
id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateProgramParams struct {
|
type CreateProgramParams struct {
|
||||||
|
|
@ -34,6 +35,7 @@ type CreateProgramParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (Program, error) {
|
func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (Program, error) {
|
||||||
|
|
@ -44,6 +46,7 @@ func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (P
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
)
|
)
|
||||||
var i Program
|
var i Program
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -56,6 +59,7 @@ func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (P
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +75,7 @@ func (q *Queries) DeleteProgram(ctx context.Context, id int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetProgramByID = `-- name: GetProgramByID :one
|
const GetProgramByID = `-- name: GetProgramByID :one
|
||||||
SELECT id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status
|
SELECT id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status, access_tier
|
||||||
FROM programs
|
FROM programs
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -89,6 +93,7 @@ func (q *Queries) GetProgramByID(ctx context.Context, id int64) (Program, error)
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -132,6 +137,7 @@ SELECT
|
||||||
p.thumbnail,
|
p.thumbnail,
|
||||||
p.sort_order,
|
p.sort_order,
|
||||||
p.publish_status,
|
p.publish_status,
|
||||||
|
p.access_tier,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM programs p
|
FROM programs p
|
||||||
|
|
@ -163,6 +169,7 @@ type ListProgramsRow struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder int32 `json:"sort_order"`
|
SortOrder int32 `json:"sort_order"`
|
||||||
PublishStatus string `json:"publish_status"`
|
PublishStatus string `json:"publish_status"`
|
||||||
|
AccessTier string `json:"access_tier"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
@ -190,6 +197,7 @@ func (q *Queries) ListPrograms(ctx context.Context, arg ListProgramsParams) ([]L
|
||||||
&i.Thumbnail,
|
&i.Thumbnail,
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -212,11 +220,12 @@ SET
|
||||||
thumbnail = COALESCE($4::text, thumbnail),
|
thumbnail = COALESCE($4::text, thumbnail),
|
||||||
sort_order = coalesce($5::int, sort_order),
|
sort_order = coalesce($5::int, sort_order),
|
||||||
publish_status = COALESCE($6::varchar, publish_status),
|
publish_status = COALESCE($6::varchar, publish_status),
|
||||||
|
access_tier = COALESCE($7::varchar, access_tier),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE
|
WHERE
|
||||||
id = $7
|
id = $8
|
||||||
RETURNING
|
RETURNING
|
||||||
id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status
|
id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status, access_tier
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateProgramParams struct {
|
type UpdateProgramParams struct {
|
||||||
|
|
@ -226,6 +235,7 @@ type UpdateProgramParams struct {
|
||||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||||
PublishStatus pgtype.Text `json:"publish_status"`
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
|
AccessTier pgtype.Text `json:"access_tier"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,6 +247,7 @@ func (q *Queries) UpdateProgram(ctx context.Context, arg UpdateProgramParams) (P
|
||||||
arg.Thumbnail,
|
arg.Thumbnail,
|
||||||
arg.SortOrder,
|
arg.SortOrder,
|
||||||
arg.PublishStatus,
|
arg.PublishStatus,
|
||||||
|
arg.AccessTier,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i Program
|
var i Program
|
||||||
|
|
@ -250,6 +261,7 @@ func (q *Queries) UpdateProgram(ctx context.Context, arg UpdateProgramParams) (P
|
||||||
&i.SortOrder,
|
&i.SortOrder,
|
||||||
&i.Category,
|
&i.Category,
|
||||||
&i.PublishStatus,
|
&i.PublishStatus,
|
||||||
|
&i.AccessTier,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
internal/domain/content_access_tier.go
Normal file
45
internal/domain/content_access_tier.go
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// ContentAccessTier controls whether learners need an active subscription to consume content.
|
||||||
|
// Effective tier cascades at read time: any PREMIUM ancestor makes descendants PREMIUM.
|
||||||
|
type ContentAccessTier string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContentAccessFree ContentAccessTier = "FREE"
|
||||||
|
ContentAccessPremium ContentAccessTier = "PREMIUM"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContentAccessTierFromDB normalizes persisted values.
|
||||||
|
func ContentAccessTierFromDB(raw string) ContentAccessTier {
|
||||||
|
switch strings.TrimSpace(strings.ToUpper(raw)) {
|
||||||
|
case string(ContentAccessFree):
|
||||||
|
return ContentAccessFree
|
||||||
|
default:
|
||||||
|
return ContentAccessPremium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentAccessTierFromCreateInput resolves create body: omit → premium; explicit value validated separately.
|
||||||
|
func ContentAccessTierFromCreateInput(raw *string) ContentAccessTier {
|
||||||
|
if raw == nil || strings.TrimSpace(*raw) == "" {
|
||||||
|
return ContentAccessPremium
|
||||||
|
}
|
||||||
|
return ContentAccessTierFromDB(*raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EffectiveContentAccessTier returns PREMIUM when any tier in the chain is PREMIUM.
|
||||||
|
func EffectiveContentAccessTier(tiers ...ContentAccessTier) ContentAccessTier {
|
||||||
|
for _, tier := range tiers {
|
||||||
|
if tier == ContentAccessPremium {
|
||||||
|
return ContentAccessPremium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ContentAccessFree
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiresSubscription is true when the effective tier needs an active plan.
|
||||||
|
func (t ContentAccessTier) RequiresSubscription() bool {
|
||||||
|
return t == ContentAccessPremium
|
||||||
|
}
|
||||||
15
internal/domain/content_access_tier_test.go
Normal file
15
internal/domain/content_access_tier_test.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestEffectiveContentAccessTier(t *testing.T) {
|
||||||
|
if got := EffectiveContentAccessTier(ContentAccessFree, ContentAccessFree); got != ContentAccessFree {
|
||||||
|
t.Fatalf("expected FREE, got %s", got)
|
||||||
|
}
|
||||||
|
if got := EffectiveContentAccessTier(ContentAccessFree, ContentAccessPremium); got != ContentAccessPremium {
|
||||||
|
t.Fatalf("expected PREMIUM, got %s", got)
|
||||||
|
}
|
||||||
|
if got := EffectiveContentAccessTier(ContentAccessPremium, ContentAccessFree); got != ContentAccessPremium {
|
||||||
|
t.Fatalf("expected cascaded PREMIUM, got %s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,8 @@ type Course struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
// Populated on list-by-program. Practice count: lms_practices rows with course_id = course only
|
// Populated on list-by-program. Practice count: lms_practices rows with course_id = course only
|
||||||
|
|
@ -46,6 +48,7 @@ type CreateCourseInput struct {
|
||||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCourseInput struct {
|
type UpdateCourseInput struct {
|
||||||
|
|
@ -54,4 +57,5 @@ type UpdateCourseInput struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ type ExamPrepCatalogCourse struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
UnitsCount *int64 `json:"units_count,omitempty"`
|
UnitsCount *int64 `json:"units_count,omitempty"`
|
||||||
ModulesCount *int64 `json:"modules_count,omitempty"`
|
ModulesCount *int64 `json:"modules_count,omitempty"`
|
||||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||||
|
|
@ -32,6 +34,7 @@ type CreateExamPrepCatalogCourseInput struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateExamPrepCatalogCourseInput struct {
|
type UpdateExamPrepCatalogCourseInput struct {
|
||||||
|
|
@ -41,4 +44,5 @@ type UpdateExamPrepCatalogCourseInput struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ type ExamPrepLesson struct {
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
@ -30,6 +32,7 @@ type CreateExamPrepLessonInput struct {
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateExamPrepLessonInput struct {
|
type UpdateExamPrepLessonInput struct {
|
||||||
|
|
@ -39,4 +42,5 @@ type UpdateExamPrepLessonInput struct {
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ type ExamPrepModule struct {
|
||||||
Icon *string `json:"icon,omitempty"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||||
PracticesCount *int64 `json:"practices_count,omitempty"`
|
PracticesCount *int64 `json:"practices_count,omitempty"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
|
|
@ -32,6 +34,7 @@ type CreateExamPrepModuleInput struct {
|
||||||
Icon *string `json:"icon,omitempty"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateExamPrepModuleInput struct {
|
type UpdateExamPrepModuleInput struct {
|
||||||
|
|
@ -41,4 +44,5 @@ type UpdateExamPrepModuleInput struct {
|
||||||
Icon *string `json:"icon,omitempty"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ type ExamPrepUnit struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
ModulesCount *int64 `json:"modules_count,omitempty"`
|
ModulesCount *int64 `json:"modules_count,omitempty"`
|
||||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||||
PracticesCount *int64 `json:"practices_count,omitempty"`
|
PracticesCount *int64 `json:"practices_count,omitempty"`
|
||||||
|
|
@ -33,6 +35,7 @@ type CreateExamPrepUnitInput struct {
|
||||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateExamPrepUnitInput struct {
|
type UpdateExamPrepUnitInput struct {
|
||||||
|
|
@ -41,4 +44,5 @@ type UpdateExamPrepUnitInput struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ type Lesson struct {
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus LessonPublishStatus `json:"publish_status"`
|
PublishStatus LessonPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
|
|
@ -61,6 +63,7 @@ type CreateLessonInput struct {
|
||||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateLessonInput struct {
|
type UpdateLessonInput struct {
|
||||||
|
|
@ -70,4 +73,5 @@ type UpdateLessonInput struct {
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ type Module struct {
|
||||||
Icon *string `json:"icon,omitempty"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
HasPractice bool `json:"has_practice"`
|
HasPractice bool `json:"has_practice"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
|
|
@ -31,6 +33,7 @@ type CreateModuleInput struct {
|
||||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateModuleInput struct {
|
type UpdateModuleInput struct {
|
||||||
|
|
@ -39,4 +42,5 @@ type UpdateModuleInput struct {
|
||||||
Icon *string `json:"icon,omitempty"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ type Program struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||||
|
AccessTier ContentAccessTier `json:"access_tier"`
|
||||||
|
EffectiveAccessTier ContentAccessTier `json:"effective_access_tier,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||||
|
|
@ -30,6 +32,7 @@ type CreateProgramInput struct {
|
||||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProgramInput struct {
|
type UpdateProgramInput struct {
|
||||||
|
|
@ -39,4 +42,5 @@ type UpdateProgramInput struct {
|
||||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||||
SortOrder *int `json:"sort_order,omitempty"`
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
|
AccessTier *string `json:"access_tier,omitempty" validate:"omitempty,oneof=FREE free PREMIUM premium"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func examPrepCatalogCourseToDomain(c dbgen.ExamPrepCatalogCourse) domain.ExamPre
|
||||||
Category: c.Category,
|
Category: c.Category,
|
||||||
SortOrder: int(c.SortOrder),
|
SortOrder: int(c.SortOrder),
|
||||||
PublishStatus: domain.ContentPublishStatusFromDB(c.PublishStatus),
|
PublishStatus: domain.ContentPublishStatusFromDB(c.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(c.AccessTier),
|
||||||
}
|
}
|
||||||
out.Description = fromPgText(c.Description)
|
out.Description = fromPgText(c.Description)
|
||||||
out.Thumbnail = fromPgText(c.Thumbnail)
|
out.Thumbnail = fromPgText(c.Thumbnail)
|
||||||
|
|
@ -36,6 +37,7 @@ func (s *Store) CreateExamPrepCatalogCourse(ctx context.Context, input domain.Cr
|
||||||
Category: input.Category,
|
Category: input.Category,
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ExamPrepCatalogCourse{}, err
|
return domain.ExamPrepCatalogCourse{}, err
|
||||||
|
|
@ -61,6 +63,7 @@ func (s *Store) GetExamPrepCatalogCourseByID(ctx context.Context, id int64) (dom
|
||||||
CreatedAt: c.CreatedAt,
|
CreatedAt: c.CreatedAt,
|
||||||
UpdatedAt: c.UpdatedAt,
|
UpdatedAt: c.UpdatedAt,
|
||||||
PublishStatus: c.PublishStatus,
|
PublishStatus: c.PublishStatus,
|
||||||
|
AccessTier: c.AccessTier,
|
||||||
})
|
})
|
||||||
out.HasPractice = c.HasPractice
|
out.HasPractice = c.HasPractice
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
@ -94,6 +97,7 @@ func (s *Store) ListExamPrepCatalogCourses(ctx context.Context, publishedOnly bo
|
||||||
CreatedAt: r.CreatedAt,
|
CreatedAt: r.CreatedAt,
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
})
|
})
|
||||||
item.UnitsCount = &r.UnitsCount
|
item.UnitsCount = &r.UnitsCount
|
||||||
item.ModulesCount = &r.ModulesCount
|
item.ModulesCount = &r.ModulesCount
|
||||||
|
|
@ -123,6 +127,7 @@ func (s *Store) UpdateExamPrepCatalogCourse(ctx context.Context, id int64, input
|
||||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||||
SortOrder: optionalInt4Update(input.SortOrder),
|
SortOrder: optionalInt4Update(input.SortOrder),
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func examPrepLessonToDomain(l dbgen.ExamPrepUnitModuleLesson) domain.ExamPrepLes
|
||||||
Title: l.Title,
|
Title: l.Title,
|
||||||
SortOrder: int(l.SortOrder),
|
SortOrder: int(l.SortOrder),
|
||||||
PublishStatus: domain.ContentPublishStatusFromDB(l.PublishStatus),
|
PublishStatus: domain.ContentPublishStatusFromDB(l.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(l.AccessTier),
|
||||||
}
|
}
|
||||||
out.VideoURL = fromPgText(l.VideoUrl)
|
out.VideoURL = fromPgText(l.VideoUrl)
|
||||||
out.Thumbnail = fromPgText(l.Thumbnail)
|
out.Thumbnail = fromPgText(l.Thumbnail)
|
||||||
|
|
@ -38,6 +39,7 @@ func (s *Store) CreateExamPrepUnitModuleLesson(ctx context.Context, unitModuleID
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
Description: toPgText(input.Description),
|
Description: toPgText(input.Description),
|
||||||
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ExamPrepLesson{}, err
|
return domain.ExamPrepLesson{}, err
|
||||||
|
|
@ -64,6 +66,7 @@ func (s *Store) GetExamPrepUnitModuleLessonByID(ctx context.Context, id int64) (
|
||||||
CreatedAt: l.CreatedAt,
|
CreatedAt: l.CreatedAt,
|
||||||
UpdatedAt: l.UpdatedAt,
|
UpdatedAt: l.UpdatedAt,
|
||||||
PublishStatus: l.PublishStatus,
|
PublishStatus: l.PublishStatus,
|
||||||
|
AccessTier: l.AccessTier,
|
||||||
})
|
})
|
||||||
out.HasPractice = l.HasPractice
|
out.HasPractice = l.HasPractice
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
@ -99,6 +102,7 @@ func (s *Store) ListExamPrepUnitModuleLessonsByUnitModuleID(ctx context.Context,
|
||||||
CreatedAt: r.CreatedAt,
|
CreatedAt: r.CreatedAt,
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
})
|
})
|
||||||
item.HasPractice = r.HasPractice
|
item.HasPractice = r.HasPractice
|
||||||
out = append(out, item)
|
out = append(out, item)
|
||||||
|
|
@ -129,6 +133,7 @@ func (s *Store) UpdateExamPrepUnitModuleLesson(ctx context.Context, id int64, in
|
||||||
Description: optionalTextUpdate(input.Description),
|
Description: optionalTextUpdate(input.Description),
|
||||||
SortOrder: optionalInt4Update(input.SortOrder),
|
SortOrder: optionalInt4Update(input.SortOrder),
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func examPrepModuleToDomain(m dbgen.ExamPrepUnitModule) domain.ExamPrepModule {
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
SortOrder: int(m.SortOrder),
|
SortOrder: int(m.SortOrder),
|
||||||
PublishStatus: domain.ContentPublishStatusFromDB(m.PublishStatus),
|
PublishStatus: domain.ContentPublishStatusFromDB(m.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(m.AccessTier),
|
||||||
}
|
}
|
||||||
out.Description = fromPgText(m.Description)
|
out.Description = fromPgText(m.Description)
|
||||||
out.Thumbnail = fromPgText(m.Thumbnail)
|
out.Thumbnail = fromPgText(m.Thumbnail)
|
||||||
|
|
@ -38,6 +39,7 @@ func (s *Store) CreateExamPrepUnitModule(ctx context.Context, unitID int64, inpu
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
Icon: toPgText(input.Icon),
|
Icon: toPgText(input.Icon),
|
||||||
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ExamPrepModule{}, err
|
return domain.ExamPrepModule{}, err
|
||||||
|
|
@ -64,6 +66,7 @@ func (s *Store) GetExamPrepUnitModuleByID(ctx context.Context, id int64) (domain
|
||||||
CreatedAt: m.CreatedAt,
|
CreatedAt: m.CreatedAt,
|
||||||
UpdatedAt: m.UpdatedAt,
|
UpdatedAt: m.UpdatedAt,
|
||||||
PublishStatus: m.PublishStatus,
|
PublishStatus: m.PublishStatus,
|
||||||
|
AccessTier: m.AccessTier,
|
||||||
})
|
})
|
||||||
out.HasPractice = m.HasPractice
|
out.HasPractice = m.HasPractice
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
@ -99,6 +102,7 @@ func (s *Store) ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64,
|
||||||
CreatedAt: r.CreatedAt,
|
CreatedAt: r.CreatedAt,
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
})
|
})
|
||||||
item.LessonsCount = &r.LessonsCount
|
item.LessonsCount = &r.LessonsCount
|
||||||
item.PracticesCount = &r.PracticesCount
|
item.PracticesCount = &r.PracticesCount
|
||||||
|
|
@ -131,6 +135,7 @@ func (s *Store) UpdateExamPrepUnitModule(ctx context.Context, id int64, input do
|
||||||
Icon: optionalTextUpdate(input.Icon),
|
Icon: optionalTextUpdate(input.Icon),
|
||||||
SortOrder: optionalInt4Update(input.SortOrder),
|
SortOrder: optionalInt4Update(input.SortOrder),
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func examPrepUnitToDomain(u dbgen.ExamPrepUnit) domain.ExamPrepUnit {
|
||||||
Name: u.Name,
|
Name: u.Name,
|
||||||
SortOrder: int(u.SortOrder),
|
SortOrder: int(u.SortOrder),
|
||||||
PublishStatus: domain.ContentPublishStatusFromDB(u.PublishStatus),
|
PublishStatus: domain.ContentPublishStatusFromDB(u.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(u.AccessTier),
|
||||||
}
|
}
|
||||||
out.Description = fromPgText(u.Description)
|
out.Description = fromPgText(u.Description)
|
||||||
out.Thumbnail = fromPgText(u.Thumbnail)
|
out.Thumbnail = fromPgText(u.Thumbnail)
|
||||||
|
|
@ -51,6 +52,7 @@ func (s *Store) CreateExamPrepUnit(ctx context.Context, catalogCourseID int64, i
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ExamPrepUnit{}, err
|
return domain.ExamPrepUnit{}, err
|
||||||
|
|
@ -68,6 +70,7 @@ func (s *Store) CreateExamPrepUnit(ctx context.Context, catalogCourseID int64, i
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ExamPrepUnit{}, err
|
return domain.ExamPrepUnit{}, err
|
||||||
|
|
@ -93,6 +96,7 @@ func (s *Store) GetExamPrepUnitByID(ctx context.Context, id int64) (domain.ExamP
|
||||||
CreatedAt: u.CreatedAt,
|
CreatedAt: u.CreatedAt,
|
||||||
UpdatedAt: u.UpdatedAt,
|
UpdatedAt: u.UpdatedAt,
|
||||||
PublishStatus: u.PublishStatus,
|
PublishStatus: u.PublishStatus,
|
||||||
|
AccessTier: u.AccessTier,
|
||||||
})
|
})
|
||||||
out.HasPractice = u.HasPractice
|
out.HasPractice = u.HasPractice
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
@ -127,6 +131,7 @@ func (s *Store) ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCou
|
||||||
CreatedAt: r.CreatedAt,
|
CreatedAt: r.CreatedAt,
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
})
|
})
|
||||||
item.ModulesCount = &r.ModulesCount
|
item.ModulesCount = &r.ModulesCount
|
||||||
item.LessonsCount = &r.LessonsCount
|
item.LessonsCount = &r.LessonsCount
|
||||||
|
|
@ -159,6 +164,7 @@ func (s *Store) UpdateExamPrepUnit(ctx context.Context, id int64, input domain.U
|
||||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||||
SortOrder: optionalInt4Update(input.SortOrder),
|
SortOrder: optionalInt4Update(input.SortOrder),
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ func courseToDomain(c dbgen.Course) domain.Course {
|
||||||
ProgramID: c.ProgramID,
|
ProgramID: c.ProgramID,
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
PublishStatus: domain.ContentPublishStatusFromDB(c.PublishStatus),
|
PublishStatus: domain.ContentPublishStatusFromDB(c.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(c.AccessTier),
|
||||||
}
|
}
|
||||||
out.Description = fromPgText(c.Description)
|
out.Description = fromPgText(c.Description)
|
||||||
out.Thumbnail = fromPgText(c.Thumbnail)
|
out.Thumbnail = fromPgText(c.Thumbnail)
|
||||||
|
|
@ -51,6 +52,7 @@ func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Course{}, err
|
return domain.Course{}, err
|
||||||
|
|
@ -68,6 +70,7 @@ func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Course{}, err
|
return domain.Course{}, err
|
||||||
|
|
@ -101,6 +104,7 @@ func (s *Store) GetCourseByID(ctx context.Context, id int64) (domain.Course, err
|
||||||
CreatedAt: c.CreatedAt,
|
CreatedAt: c.CreatedAt,
|
||||||
UpdatedAt: c.UpdatedAt,
|
UpdatedAt: c.UpdatedAt,
|
||||||
PublishStatus: c.PublishStatus,
|
PublishStatus: c.PublishStatus,
|
||||||
|
AccessTier: c.AccessTier,
|
||||||
})
|
})
|
||||||
out.HasPractice = c.HasPractice
|
out.HasPractice = c.HasPractice
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
@ -135,6 +139,7 @@ func (s *Store) ListCoursesByProgramID(ctx context.Context, programID int64, pub
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
SortOrder: r.SortOrder,
|
SortOrder: r.SortOrder,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
})
|
})
|
||||||
co.ModuleCount = int(r.ModuleCount)
|
co.ModuleCount = int(r.ModuleCount)
|
||||||
co.LessonCount = int(r.LessonCount)
|
co.LessonCount = int(r.LessonCount)
|
||||||
|
|
@ -177,6 +182,7 @@ func (s *Store) UpdateCourse(ctx context.Context, id int64, input domain.UpdateC
|
||||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Course{}, err
|
return domain.Course{}, err
|
||||||
|
|
@ -198,6 +204,7 @@ func (s *Store) UpdateCourse(ctx context.Context, id int64, input domain.UpdateC
|
||||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||||
SortOrder: sortParam,
|
SortOrder: sortParam,
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ func lessonToDomain(l dbgen.Lesson) domain.Lesson {
|
||||||
ModuleID: l.ModuleID,
|
ModuleID: l.ModuleID,
|
||||||
Title: l.Title,
|
Title: l.Title,
|
||||||
PublishStatus: domain.LessonPublishStatusFromDB(l.PublishStatus),
|
PublishStatus: domain.LessonPublishStatusFromDB(l.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(l.AccessTier),
|
||||||
}
|
}
|
||||||
out.VideoURL = fromPgText(l.VideoUrl)
|
out.VideoURL = fromPgText(l.VideoUrl)
|
||||||
out.Thumbnail = fromPgText(l.Thumbnail)
|
out.Thumbnail = fromPgText(l.Thumbnail)
|
||||||
|
|
@ -54,6 +55,7 @@ func (s *Store) CreateLesson(ctx context.Context, moduleID int64, input domain.C
|
||||||
Description: toPgText(input.Description),
|
Description: toPgText(input.Description),
|
||||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Lesson{}, err
|
return domain.Lesson{}, err
|
||||||
|
|
@ -72,6 +74,7 @@ func (s *Store) CreateLesson(ctx context.Context, moduleID int64, input domain.C
|
||||||
Description: toPgText(input.Description),
|
Description: toPgText(input.Description),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Lesson{}, err
|
return domain.Lesson{}, err
|
||||||
|
|
@ -96,6 +99,7 @@ func (s *Store) GetLessonByID(ctx context.Context, id int64) (domain.Lesson, err
|
||||||
Description: l.Description,
|
Description: l.Description,
|
||||||
SortOrder: l.SortOrder,
|
SortOrder: l.SortOrder,
|
||||||
PublishStatus: l.PublishStatus,
|
PublishStatus: l.PublishStatus,
|
||||||
|
AccessTier: l.AccessTier,
|
||||||
CreatedAt: l.CreatedAt,
|
CreatedAt: l.CreatedAt,
|
||||||
UpdatedAt: l.UpdatedAt,
|
UpdatedAt: l.UpdatedAt,
|
||||||
})
|
})
|
||||||
|
|
@ -135,6 +139,7 @@ func (s *Store) ListLessonsByModuleID(ctx context.Context, moduleID int64, publi
|
||||||
Description: r.Description,
|
Description: r.Description,
|
||||||
SortOrder: r.SortOrder,
|
SortOrder: r.SortOrder,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
CreatedAt: r.CreatedAt,
|
CreatedAt: r.CreatedAt,
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
})
|
})
|
||||||
|
|
@ -179,6 +184,7 @@ func (s *Store) UpdateLesson(ctx context.Context, id int64, input domain.UpdateL
|
||||||
Description: optionalTextUpdate(input.Description),
|
Description: optionalTextUpdate(input.Description),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: pubParam,
|
PublishStatus: pubParam,
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Lesson{}, err
|
return domain.Lesson{}, err
|
||||||
|
|
@ -201,6 +207,7 @@ func (s *Store) UpdateLesson(ctx context.Context, id int64, input domain.UpdateL
|
||||||
Description: optionalTextUpdate(input.Description),
|
Description: optionalTextUpdate(input.Description),
|
||||||
SortOrder: sortParam,
|
SortOrder: sortParam,
|
||||||
PublishStatus: pubParam,
|
PublishStatus: pubParam,
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func moduleToDomain(m dbgen.Module) domain.Module {
|
||||||
CourseID: m.CourseID,
|
CourseID: m.CourseID,
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
PublishStatus: domain.ContentPublishStatusFromDB(m.PublishStatus),
|
PublishStatus: domain.ContentPublishStatusFromDB(m.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(m.AccessTier),
|
||||||
}
|
}
|
||||||
out.Description = fromPgText(m.Description)
|
out.Description = fromPgText(m.Description)
|
||||||
out.Icon = fromPgText(m.Icon)
|
out.Icon = fromPgText(m.Icon)
|
||||||
|
|
@ -53,6 +54,7 @@ func (s *Store) CreateModule(ctx context.Context, programID, courseID int64, inp
|
||||||
Icon: toPgText(input.Icon),
|
Icon: toPgText(input.Icon),
|
||||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Module{}, err
|
return domain.Module{}, err
|
||||||
|
|
@ -71,6 +73,7 @@ func (s *Store) CreateModule(ctx context.Context, programID, courseID int64, inp
|
||||||
Icon: toPgText(input.Icon),
|
Icon: toPgText(input.Icon),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Module{}, err
|
return domain.Module{}, err
|
||||||
|
|
@ -105,6 +108,7 @@ func (s *Store) GetModuleByID(ctx context.Context, id int64) (domain.Module, err
|
||||||
CreatedAt: m.CreatedAt,
|
CreatedAt: m.CreatedAt,
|
||||||
UpdatedAt: m.UpdatedAt,
|
UpdatedAt: m.UpdatedAt,
|
||||||
PublishStatus: m.PublishStatus,
|
PublishStatus: m.PublishStatus,
|
||||||
|
AccessTier: m.AccessTier,
|
||||||
})
|
})
|
||||||
out.HasPractice = m.HasPractice
|
out.HasPractice = m.HasPractice
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
@ -141,6 +145,7 @@ func (s *Store) ListModulesByProgramAndCourse(ctx context.Context, programID, co
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
SortOrder: r.SortOrder,
|
SortOrder: r.SortOrder,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
})
|
})
|
||||||
mod.HasPractice = r.HasPractice
|
mod.HasPractice = r.HasPractice
|
||||||
out = append(out, mod)
|
out = append(out, mod)
|
||||||
|
|
@ -185,6 +190,7 @@ func (s *Store) UpdateModule(ctx context.Context, id int64, input domain.UpdateM
|
||||||
Icon: optionalTextUpdate(input.Icon),
|
Icon: optionalTextUpdate(input.Icon),
|
||||||
SortOrder: sortParam,
|
SortOrder: sortParam,
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func programToDomain(p dbgen.Program) domain.Program {
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Category: p.Category,
|
Category: p.Category,
|
||||||
PublishStatus: domain.ContentPublishStatusFromDB(p.PublishStatus),
|
PublishStatus: domain.ContentPublishStatusFromDB(p.PublishStatus),
|
||||||
|
AccessTier: domain.ContentAccessTierFromDB(p.AccessTier),
|
||||||
}
|
}
|
||||||
out.Description = fromPgText(p.Description)
|
out.Description = fromPgText(p.Description)
|
||||||
out.Thumbnail = fromPgText(p.Thumbnail)
|
out.Thumbnail = fromPgText(p.Thumbnail)
|
||||||
|
|
@ -49,6 +50,7 @@ func (s *Store) CreateProgram(ctx context.Context, input domain.CreateProgramInp
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Program{}, err
|
return domain.Program{}, err
|
||||||
|
|
@ -66,6 +68,7 @@ func (s *Store) CreateProgram(ctx context.Context, input domain.CreateProgramInp
|
||||||
Thumbnail: toPgText(input.Thumbnail),
|
Thumbnail: toPgText(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: pub,
|
PublishStatus: pub,
|
||||||
|
AccessTier: contentAccessTierForCreate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Program{}, err
|
return domain.Program{}, err
|
||||||
|
|
@ -117,6 +120,7 @@ func (s *Store) ListPrograms(ctx context.Context, publishedOnly bool, category s
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
SortOrder: r.SortOrder,
|
SortOrder: r.SortOrder,
|
||||||
PublishStatus: r.PublishStatus,
|
PublishStatus: r.PublishStatus,
|
||||||
|
AccessTier: r.AccessTier,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
return out, total, nil
|
return out, total, nil
|
||||||
|
|
@ -129,6 +133,23 @@ func optionalTextUpdate(val *string) pgtype.Text {
|
||||||
return pgtype.Text{String: *val, Valid: true}
|
return pgtype.Text{String: *val, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contentAccessTierForCreate(raw *string) string {
|
||||||
|
return string(domain.ContentAccessTierFromCreateInput(raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
func optionalAccessTierUpdate(val *string) pgtype.Text {
|
||||||
|
if val == nil {
|
||||||
|
return pgtype.Text{Valid: false}
|
||||||
|
}
|
||||||
|
s := strings.TrimSpace(strings.ToUpper(*val))
|
||||||
|
switch s {
|
||||||
|
case string(domain.ContentAccessFree), string(domain.ContentAccessPremium):
|
||||||
|
return pgtype.Text{String: s, Valid: true}
|
||||||
|
default:
|
||||||
|
return pgtype.Text{Valid: false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func optionalPublishStatusUpdate(val *string) pgtype.Text {
|
func optionalPublishStatusUpdate(val *string) pgtype.Text {
|
||||||
if val == nil {
|
if val == nil {
|
||||||
return pgtype.Text{Valid: false}
|
return pgtype.Text{Valid: false}
|
||||||
|
|
@ -181,6 +202,7 @@ func (s *Store) UpdateProgram(ctx context.Context, id int64, input domain.Update
|
||||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||||
SortOrder: pgtype.Int4{Valid: false},
|
SortOrder: pgtype.Int4{Valid: false},
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Program{}, err
|
return domain.Program{}, err
|
||||||
|
|
@ -207,6 +229,7 @@ func (s *Store) UpdateProgram(ctx context.Context, id int64, input domain.Update
|
||||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||||
SortOrder: sortParam,
|
SortOrder: sortParam,
|
||||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
|
AccessTier: optionalAccessTierUpdate(input.AccessTier),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
335
internal/web_server/content_access_middleware.go
Normal file
335
internal/web_server/content_access_middleware.go
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
package httpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"Yimaru-Backend/internal/domain"
|
||||||
|
lessonsvc "Yimaru-Backend/internal/services/lessons"
|
||||||
|
coursessvc "Yimaru-Backend/internal/services/courses"
|
||||||
|
modulesvc "Yimaru-Backend/internal/services/modules"
|
||||||
|
programssvc "Yimaru-Backend/internal/services/programs"
|
||||||
|
practicessvc "Yimaru-Backend/internal/services/practices"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) RequireLMSSubscriptionUnlessFree() fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
role, userID, err := subscriptionScopedUser(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bypassSubscriptionForRole(role) {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
if role != domain.RoleStudent && role != domain.RoleOpenLearner {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
if domain.CategorySubscriptionGateDisabled {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
tier, resolved, err := a.resolveLMSEffectiveAccessTier(c)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, programssvc.ErrProgramNotFound),
|
||||||
|
errors.Is(err, coursessvc.ErrCourseNotFound),
|
||||||
|
errors.Is(err, modulesvc.ErrModuleNotFound),
|
||||||
|
errors.Is(err, lessonsvc.ErrLessonNotFound),
|
||||||
|
errors.Is(err, practicessvc.ErrPracticeNotFound):
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||||
|
default:
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify content access")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !resolved || !tier.RequiresSubscription() {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
active, err := a.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, domain.SubscriptionCategoryLearnEnglish)
|
||||||
|
if err != nil {
|
||||||
|
a.mongoLoggerSvc.Error("category subscription check failed",
|
||||||
|
zap.Int64("userID", userID),
|
||||||
|
zap.String("category", string(domain.SubscriptionCategoryLearnEnglish)),
|
||||||
|
zap.String("path", c.Path()),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify subscription")
|
||||||
|
}
|
||||||
|
if !active {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, fmt.Sprintf("An active %s subscription is required", humanizeSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish)))
|
||||||
|
}
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) resolveLMSEffectiveAccessTier(c *fiber.Ctx) (domain.ContentAccessTier, bool, error) {
|
||||||
|
ctx := c.Context()
|
||||||
|
routePath := c.Route().Path
|
||||||
|
|
||||||
|
if strings.Contains(routePath, "/practices/:practiceId/questions") || strings.Contains(routePath, "/practices/:id") {
|
||||||
|
practiceID, ok, err := parseRouteInt64(c, "practiceId")
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
practiceID, ok, err = parseRouteInt64(c, "id")
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
goto unresolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.lmsEffectiveTierForPractice(ctx, practiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lessonID, ok, err := parseRouteInt64(c, "lessonId"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.lmsEffectiveTierForLesson(ctx, lessonID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if moduleID, ok, err := parseRouteInt64(c, "moduleId"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.lmsEffectiveTierForModule(ctx, moduleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok, err := parseRouteInt64(c, "courseId"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(routePath, "/lessons/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.lmsEffectiveTierForLesson(ctx, id)
|
||||||
|
}
|
||||||
|
case strings.Contains(routePath, "/modules/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.lmsEffectiveTierForModule(ctx, id)
|
||||||
|
}
|
||||||
|
case strings.Contains(routePath, "/courses/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.lmsEffectiveTierForCourse(ctx, id)
|
||||||
|
}
|
||||||
|
case strings.Contains(routePath, "/programs/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.lmsEffectiveTierForProgram(ctx, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unresolved:
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) lmsEffectiveTierForProgram(ctx context.Context, programID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
program, err := a.programSvc.GetByID(ctx, programID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return program.AccessTier, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) lmsEffectiveTierForCourse(ctx context.Context, courseID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
course, err := a.courseSvc.GetByID(ctx, courseID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
program, err := a.programSvc.GetByID(ctx, course.ProgramID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) lmsEffectiveTierForModule(ctx context.Context, moduleID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
module, err := a.moduleSvc.GetByID(ctx, moduleID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
course, err := a.courseSvc.GetByID(ctx, module.CourseID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
program, err := a.programSvc.GetByID(ctx, course.ProgramID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier, module.AccessTier), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) lmsEffectiveTierForLesson(ctx context.Context, lessonID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
lesson, err := a.lessonSvc.GetByID(ctx, lessonID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
module, err := a.moduleSvc.GetByID(ctx, lesson.ModuleID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
course, err := a.courseSvc.GetByID(ctx, module.CourseID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
program, err := a.programSvc.GetByID(ctx, course.ProgramID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier, module.AccessTier, lesson.AccessTier), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) resolveExamPrepEffectiveAccessTier(c *fiber.Ctx) (domain.ContentAccessTier, bool, error) {
|
||||||
|
ctx := c.Context()
|
||||||
|
routePath := c.Route().Path
|
||||||
|
|
||||||
|
if _, ok, err := parseRouteInt64(c, "catalogCourseId"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
if _, ok, err := parseRouteInt64(c, "unitId"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
if _, ok, err := parseRouteInt64(c, "moduleId"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
if _, ok, err := parseRouteInt64(c, "lessonId"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(routePath, "/catalog-courses/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.examPrepEffectiveTierForCatalogCourse(ctx, id)
|
||||||
|
}
|
||||||
|
case strings.Contains(routePath, "/units/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.examPrepEffectiveTierForUnit(ctx, id)
|
||||||
|
}
|
||||||
|
case strings.Contains(routePath, "/modules/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.examPrepEffectiveTierForModule(ctx, id)
|
||||||
|
}
|
||||||
|
case strings.Contains(routePath, "/lessons/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.examPrepEffectiveTierForLesson(ctx, id)
|
||||||
|
}
|
||||||
|
case strings.Contains(routePath, "/practices/:id"):
|
||||||
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return a.examPrepEffectiveTierForPractice(ctx, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) examPrepEffectiveTierForCatalogCourse(ctx context.Context, catalogCourseID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, catalogCourseID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return cc.AccessTier, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) examPrepEffectiveTierForUnit(ctx context.Context, unitID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
unit, err := a.examPrepSvc.GetUnitByID(ctx, unitID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, unit.CatalogCourseID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return domain.EffectiveContentAccessTier(cc.AccessTier, unit.AccessTier), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) examPrepEffectiveTierForModule(ctx context.Context, moduleID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
module, err := a.examPrepSvc.GetModuleByID(ctx, moduleID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
unit, err := a.examPrepSvc.GetUnitByID(ctx, module.UnitID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, unit.CatalogCourseID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return domain.EffectiveContentAccessTier(cc.AccessTier, unit.AccessTier, module.AccessTier), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) examPrepEffectiveTierForLesson(ctx context.Context, lessonID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
lesson, err := a.examPrepSvc.GetLessonByID(ctx, lessonID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
module, err := a.examPrepSvc.GetModuleByID(ctx, lesson.UnitModuleID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
unit, err := a.examPrepSvc.GetUnitByID(ctx, module.UnitID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, unit.CatalogCourseID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return domain.EffectiveContentAccessTier(cc.AccessTier, unit.AccessTier, module.AccessTier, lesson.AccessTier), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) examPrepEffectiveTierForPractice(ctx context.Context, practiceID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
practice, err := a.examPrepSvc.GetExamPrepPracticeByID(ctx, practiceID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return a.examPrepEffectiveTierForLesson(ctx, practice.LessonID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) lmsEffectiveTierForPractice(ctx context.Context, practiceID int64) (domain.ContentAccessTier, bool, error) {
|
||||||
|
practice, err := a.practiceSvc.GetByID(ctx, practiceID)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
switch practice.ParentKind {
|
||||||
|
case domain.ParentKindLesson:
|
||||||
|
return a.lmsEffectiveTierForLesson(ctx, practice.ParentID)
|
||||||
|
case domain.ParentKindModule:
|
||||||
|
return a.lmsEffectiveTierForModule(ctx, practice.ParentID)
|
||||||
|
case domain.ParentKindCourse:
|
||||||
|
return a.lmsEffectiveTierForCourse(ctx, practice.ParentID)
|
||||||
|
default:
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
115
internal/web_server/handlers/content_access_gate.go
Normal file
115
internal/web_server/handlers/content_access_gate.go
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Yimaru-Backend/internal/domain"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) learnerHasLearnEnglishSubscription(c *fiber.Ctx) (bool, error) {
|
||||||
|
return h.learnerHasSubscriptionCategory(c, domain.SubscriptionCategoryLearnEnglish)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ensureLearnerPremiumContentAccess(c *fiber.Ctx, effectiveTier domain.ContentAccessTier, category domain.SubscriptionCategory) error {
|
||||||
|
role, _ := c.Locals("role").(domain.Role)
|
||||||
|
if !role.IsCustomerLearnerRole() || domain.CategorySubscriptionGateDisabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !effectiveTier.RequiresSubscription() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
active, err := h.learnerHasSubscriptionCategory(c, category)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: fmt.Sprintf("Failed to verify %s subscription", category),
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !active {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
|
||||||
|
Message: fmt.Sprintf("An active %s subscription is required", humanizeSubscriptionCategory(category)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyProgramEffectiveAccessTier(p *domain.Program) {
|
||||||
|
p.EffectiveAccessTier = p.AccessTier
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyCourseEffectiveAccessTier(course *domain.Course, program domain.Program) {
|
||||||
|
course.EffectiveAccessTier = domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyModuleEffectiveAccessTier(module *domain.Module, program domain.Program, course domain.Course) {
|
||||||
|
module.EffectiveAccessTier = domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier, module.AccessTier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyLessonEffectiveAccessTier(lesson *domain.Lesson, program domain.Program, course domain.Course, module domain.Module) {
|
||||||
|
lesson.EffectiveAccessTier = domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier, module.AccessTier, lesson.AccessTier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyExamPrepCatalogCourseEffectiveAccessTier(cc *domain.ExamPrepCatalogCourse) {
|
||||||
|
cc.EffectiveAccessTier = cc.AccessTier
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyExamPrepUnitEffectiveAccessTier(unit *domain.ExamPrepUnit, catalogCourse domain.ExamPrepCatalogCourse) {
|
||||||
|
unit.EffectiveAccessTier = domain.EffectiveContentAccessTier(catalogCourse.AccessTier, unit.AccessTier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyExamPrepModuleEffectiveAccessTier(module *domain.ExamPrepModule, catalogCourse domain.ExamPrepCatalogCourse, unit domain.ExamPrepUnit) {
|
||||||
|
module.EffectiveAccessTier = domain.EffectiveContentAccessTier(catalogCourse.AccessTier, unit.AccessTier, module.AccessTier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyExamPrepLessonEffectiveAccessTier(lesson *domain.ExamPrepLesson, catalogCourse domain.ExamPrepCatalogCourse, unit domain.ExamPrepUnit, module domain.ExamPrepModule) {
|
||||||
|
lesson.EffectiveAccessTier = domain.EffectiveContentAccessTier(catalogCourse.AccessTier, unit.AccessTier, module.AccessTier, lesson.AccessTier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func learnerCanViewEffectiveTier(hasSubscription bool, effectiveTier domain.ContentAccessTier) bool {
|
||||||
|
return !effectiveTier.RequiresSubscription() || hasSubscription
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterProgramsForLearner(items []domain.Program, hasLearnEnglish bool) []domain.Program {
|
||||||
|
filtered := make([]domain.Program, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
applyProgramEffectiveAccessTier(&item)
|
||||||
|
if learnerCanViewEffectiveTier(hasLearnEnglish, item.EffectiveAccessTier) {
|
||||||
|
filtered = append(filtered, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCoursesForLearner(items []domain.Course, program domain.Program, hasLearnEnglish bool) []domain.Course {
|
||||||
|
filtered := make([]domain.Course, 0, len(items))
|
||||||
|
for i := range items {
|
||||||
|
applyCourseEffectiveAccessTier(&items[i], program)
|
||||||
|
if learnerCanViewEffectiveTier(hasLearnEnglish, items[i].EffectiveAccessTier) {
|
||||||
|
filtered = append(filtered, items[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterExamPrepCatalogCoursesForLearner(items []domain.ExamPrepCatalogCourse, hasIELTS, hasDuolingo bool) []domain.ExamPrepCatalogCourse {
|
||||||
|
filtered := make([]domain.ExamPrepCatalogCourse, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
applyExamPrepCatalogCourseEffectiveAccessTier(&item)
|
||||||
|
if !item.EffectiveAccessTier.RequiresSubscription() {
|
||||||
|
filtered = append(filtered, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch domain.SubscriptionCategory(item.Category) {
|
||||||
|
case domain.SubscriptionCategoryIELTS:
|
||||||
|
if hasIELTS {
|
||||||
|
filtered = append(filtered, item)
|
||||||
|
}
|
||||||
|
case domain.SubscriptionCategoryDuolingo:
|
||||||
|
if hasDuolingo {
|
||||||
|
filtered = append(filtered, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
@ -93,6 +93,8 @@ func (h *Handler) ListCoursesByProgram(c *fiber.Ctx) error {
|
||||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||||
publishedOnly := !h.canManageLMSCourses(c)
|
publishedOnly := !h.canManageLMSCourses(c)
|
||||||
|
role := c.Locals("role").(domain.Role)
|
||||||
|
var program domain.Program
|
||||||
if publishedOnly {
|
if publishedOnly {
|
||||||
// Draft programs hide their courses from non-managers.
|
// Draft programs hide their courses from non-managers.
|
||||||
p, err := h.programSvc.GetByID(c.Context(), programID)
|
p, err := h.programSvc.GetByID(c.Context(), programID)
|
||||||
|
|
@ -117,6 +119,7 @@ func (h *Handler) ListCoursesByProgram(c *fiber.Ctx) error {
|
||||||
if err := h.blockLearnerIfNotLMSProgram(c, p); err != nil {
|
if err := h.blockLearnerIfNotLMSProgram(c, p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
program = p
|
||||||
}
|
}
|
||||||
items, total, err := h.courseSvc.ListByProgram(c.Context(), programID, publishedOnly, int32(limit), int32(offset))
|
items, total, err := h.courseSvc.ListByProgram(c.Context(), programID, publishedOnly, int32(limit), int32(offset))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -131,8 +134,18 @@ func (h *Handler) ListCoursesByProgram(c *fiber.Ctx) error {
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if role.IsCustomerLearnerRole() && publishedOnly {
|
||||||
|
hasLearnEnglish, err := h.learnerHasLearnEnglishSubscription(c)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to verify Learn English subscription",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
items = filterCoursesForLearner(items, program, hasLearnEnglish)
|
||||||
|
total = int64(len(items))
|
||||||
|
}
|
||||||
uid := c.Locals("user_id").(int64)
|
uid := c.Locals("user_id").(int64)
|
||||||
role := c.Locals("role").(domain.Role)
|
|
||||||
for i := range items {
|
for i := range items {
|
||||||
if err := h.lmsProgressSvc.ApplyAccessCourse(c.Context(), role, uid, &items[i]); err != nil {
|
if err := h.lmsProgressSvc.ApplyAccessCourse(c.Context(), role, uid, &items[i]); err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -205,6 +218,10 @@ func (h *Handler) GetCourse(c *fiber.Ctx) error {
|
||||||
if err := h.blockLearnerIfNotLMSProgram(c, p); err != nil {
|
if err := h.blockLearnerIfNotLMSProgram(c, p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
applyCourseEffectiveAccessTier(&course, p)
|
||||||
|
if err := h.ensureLearnerPremiumContentAccess(c, course.EffectiveAccessTier, domain.SubscriptionCategoryLearnEnglish); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
uid := c.Locals("user_id").(int64)
|
uid := c.Locals("user_id").(int64)
|
||||||
role := c.Locals("role").(domain.Role)
|
role := c.Locals("role").(domain.Role)
|
||||||
if err := h.lmsProgressSvc.ApplyAccessCourse(c.Context(), role, uid, &course); err != nil {
|
if err := h.lmsProgressSvc.ApplyAccessCourse(c.Context(), role, uid, &course); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,8 @@ func (h *Handler) GetExamPrepCatalogCourseByID(c *fiber.Ctx) error {
|
||||||
Error: examprep.ErrCatalogCourseNotFound.Error(),
|
Error: examprep.ErrCatalogCourseNotFound.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err := h.ensureLearnerExamPrepContentAccess(c, out.Category); err != nil {
|
applyExamPrepCatalogCourseEffectiveAccessTier(&out)
|
||||||
|
if err := h.ensureLearnerExamPrepContentAccess(c, out.Category, out.EffectiveAccessTier); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := h.applyExamPrepAccessCatalogCourse(c.Context(), c, &out); err != nil {
|
if err := h.applyExamPrepAccessCatalogCourse(c.Context(), c, &out); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,17 @@ func (h *Handler) ListPrograms(c *fiber.Ctx) error {
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if role.IsCustomerLearnerRole() {
|
||||||
|
hasLearnEnglish, err := h.learnerHasLearnEnglishSubscription(c)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to verify Learn English subscription",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
items = filterProgramsForLearner(items, hasLearnEnglish)
|
||||||
|
total = int64(len(items))
|
||||||
|
}
|
||||||
uid := c.Locals("user_id").(int64)
|
uid := c.Locals("user_id").(int64)
|
||||||
for i := range items {
|
for i := range items {
|
||||||
if err := h.lmsProgressSvc.ApplyAccessProgram(c.Context(), role, uid, &items[i]); err != nil {
|
if err := h.lmsProgressSvc.ApplyAccessProgram(c.Context(), role, uid, &items[i]); err != nil {
|
||||||
|
|
@ -142,6 +153,10 @@ func (h *Handler) GetProgram(c *fiber.Ctx) error {
|
||||||
if err := h.blockLearnerIfNotLMSProgram(c, p); err != nil {
|
if err := h.blockLearnerIfNotLMSProgram(c, p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
applyProgramEffectiveAccessTier(&p)
|
||||||
|
if err := h.ensureLearnerPremiumContentAccess(c, p.EffectiveAccessTier, domain.SubscriptionCategoryLearnEnglish); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
uid := c.Locals("user_id").(int64)
|
uid := c.Locals("user_id").(int64)
|
||||||
role := c.Locals("role").(domain.Role)
|
role := c.Locals("role").(domain.Role)
|
||||||
if err := h.lmsProgressSvc.ApplyAccessProgram(c.Context(), role, uid, &p); err != nil {
|
if err := h.lmsProgressSvc.ApplyAccessProgram(c.Context(), role, uid, &p); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
@ -15,7 +14,7 @@ func (h *Handler) learnerHasSubscriptionCategory(c *fiber.Ctx, category domain.S
|
||||||
return h.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, category)
|
return h.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ensureLearnerExamPrepContentAccess(c *fiber.Ctx, contentCategory string) error {
|
func (h *Handler) ensureLearnerExamPrepContentAccess(c *fiber.Ctx, contentCategory string, effectiveTier domain.ContentAccessTier) error {
|
||||||
role, _ := c.Locals("role").(domain.Role)
|
role, _ := c.Locals("role").(domain.Role)
|
||||||
if !role.IsCustomerLearnerRole() {
|
if !role.IsCustomerLearnerRole() {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -25,19 +24,7 @@ func (h *Handler) ensureLearnerExamPrepContentAccess(c *fiber.Ctx, contentCatego
|
||||||
Message: "Catalog course not found",
|
Message: "Catalog course not found",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
active, err := h.learnerHasSubscriptionCategory(c, domain.SubscriptionCategory(contentCategory))
|
return h.ensureLearnerPremiumContentAccess(c, effectiveTier, domain.SubscriptionCategory(contentCategory))
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
||||||
Message: fmt.Sprintf("Failed to verify %s subscription", contentCategory),
|
|
||||||
Error: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if !active {
|
|
||||||
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
|
|
||||||
Message: fmt.Sprintf("An active %s subscription is required", humanizeSubscriptionCategory(domain.SubscriptionCategory(contentCategory))),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) blockLearnerIfNotLMSProgram(c *fiber.Ctx, program domain.Program) error {
|
func (h *Handler) blockLearnerIfNotLMSProgram(c *fiber.Ctx, program domain.Program) error {
|
||||||
|
|
@ -66,19 +53,3 @@ func humanizeSubscriptionCategory(category domain.SubscriptionCategory) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterExamPrepCatalogCoursesForLearner(items []domain.ExamPrepCatalogCourse, hasIELTS, hasDuolingo bool) []domain.ExamPrepCatalogCourse {
|
|
||||||
filtered := make([]domain.ExamPrepCatalogCourse, 0, len(items))
|
|
||||||
for _, item := range items {
|
|
||||||
switch domain.SubscriptionCategory(item.Category) {
|
|
||||||
case domain.SubscriptionCategoryIELTS:
|
|
||||||
if hasIELTS {
|
|
||||||
filtered = append(filtered, item)
|
|
||||||
}
|
|
||||||
case domain.SubscriptionCategoryDuolingo:
|
|
||||||
if hasDuolingo {
|
|
||||||
filtered = append(filtered, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -282,17 +282,23 @@ func (a *App) RequireExamPrepSubscription() fiber.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !scoped {
|
if !scoped {
|
||||||
hasIELTS, err := a.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, domain.SubscriptionCategoryIELTS)
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
tier, tierResolved, err := a.resolveExamPrepEffectiveAccessTier(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify subscription")
|
switch {
|
||||||
|
case errors.Is(err, examprepsvc.ErrCatalogCourseNotFound),
|
||||||
|
errors.Is(err, examprepsvc.ErrUnitNotFound),
|
||||||
|
errors.Is(err, examprepsvc.ErrModuleNotFound),
|
||||||
|
errors.Is(err, examprepsvc.ErrLessonNotFound),
|
||||||
|
errors.Is(err, examprepsvc.ErrPracticeNotFound):
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||||
|
default:
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify content access")
|
||||||
}
|
}
|
||||||
hasDuolingo, err := a.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, domain.SubscriptionCategoryDuolingo)
|
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify subscription")
|
|
||||||
}
|
|
||||||
if !hasIELTS && !hasDuolingo {
|
|
||||||
return fiber.NewError(fiber.StatusForbidden, "An active IELTS or Duolingo subscription is required")
|
|
||||||
}
|
}
|
||||||
|
if tierResolved && !tier.RequiresSubscription() {
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,11 +84,11 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
// Programs (LMS top-level)
|
// Programs (LMS top-level)
|
||||||
groupV1.Post("/programs", a.authMiddleware, a.RequirePermission("programs.create"), h.CreateProgram)
|
groupV1.Post("/programs", a.authMiddleware, a.RequirePermission("programs.create"), h.CreateProgram)
|
||||||
groupV1.Get("/programs", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("programs.list"), h.ListPrograms)
|
groupV1.Get("/programs", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("programs.list"), h.ListPrograms)
|
||||||
groupV1.Put("/programs/reorder", a.authMiddleware, a.RequirePermission("programs.reorder"), h.ReorderPrograms)
|
groupV1.Put("/programs/reorder", a.authMiddleware, a.RequirePermission("programs.reorder"), h.ReorderPrograms)
|
||||||
groupV1.Get("/lms/progress", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("lms.get_my_progress"), h.GetMyLMSProgress)
|
groupV1.Get("/lms/progress", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("lms.get_my_progress"), h.GetMyLMSProgress)
|
||||||
groupV1.Get("/lms/progress-summary", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("lms.get_my_progress"), h.GetMyLMSProgressSummary)
|
groupV1.Get("/lms/progress-summary", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("lms.get_my_progress"), h.GetMyLMSProgressSummary)
|
||||||
groupV1.Get("/programs/:id", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("programs.get"), h.GetProgram)
|
groupV1.Get("/programs/:id", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("programs.get"), h.GetProgram)
|
||||||
groupV1.Put("/programs/:id", a.authMiddleware, a.RequirePermission("programs.update"), h.UpdateProgram)
|
groupV1.Put("/programs/:id", a.authMiddleware, a.RequirePermission("programs.update"), h.UpdateProgram)
|
||||||
groupV1.Delete("/programs/:id", a.authMiddleware, a.RequirePermission("programs.delete"), h.DeleteProgram)
|
groupV1.Delete("/programs/:id", a.authMiddleware, a.RequirePermission("programs.delete"), h.DeleteProgram)
|
||||||
|
|
||||||
|
|
@ -133,33 +133,33 @@ func (a *App) initAppRoutes() {
|
||||||
// Courses
|
// Courses
|
||||||
groupV1.Post("/programs/:id/courses", a.authMiddleware, a.RequirePermission("courses.create"), h.CreateCourse)
|
groupV1.Post("/programs/:id/courses", a.authMiddleware, a.RequirePermission("courses.create"), h.CreateCourse)
|
||||||
groupV1.Put("/programs/:id/courses/reorder", a.authMiddleware, a.RequirePermission("courses.reorder"), h.ReorderCoursesInProgram)
|
groupV1.Put("/programs/:id/courses/reorder", a.authMiddleware, a.RequirePermission("courses.reorder"), h.ReorderCoursesInProgram)
|
||||||
groupV1.Get("/programs/:id/courses", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("courses.list_by_program"), h.ListCoursesByProgram)
|
groupV1.Get("/programs/:id/courses", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("courses.list_by_program"), h.ListCoursesByProgram)
|
||||||
groupV1.Get("/courses/:id/practices", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("practices.list"), h.ListPracticesByCourse)
|
groupV1.Get("/courses/:id/practices", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("practices.list"), h.ListPracticesByCourse)
|
||||||
groupV1.Get("/courses/:id", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("courses.get"), h.GetCourse)
|
groupV1.Get("/courses/:id", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("courses.get"), h.GetCourse)
|
||||||
groupV1.Put("/courses/:id", a.authMiddleware, a.RequirePermission("courses.update"), h.UpdateCourse)
|
groupV1.Put("/courses/:id", a.authMiddleware, a.RequirePermission("courses.update"), h.UpdateCourse)
|
||||||
groupV1.Delete("/courses/:id", a.authMiddleware, a.RequirePermission("courses.delete"), h.DeleteCourse)
|
groupV1.Delete("/courses/:id", a.authMiddleware, a.RequirePermission("courses.delete"), h.DeleteCourse)
|
||||||
groupV1.Post("/courses/:courseId/modules", a.authMiddleware, a.RequirePermission("modules.create"), h.CreateModule)
|
groupV1.Post("/courses/:courseId/modules", a.authMiddleware, a.RequirePermission("modules.create"), h.CreateModule)
|
||||||
groupV1.Put("/courses/:courseId/modules/reorder", a.authMiddleware, a.RequirePermission("modules.reorder"), h.ReorderModulesInCourse)
|
groupV1.Put("/courses/:courseId/modules/reorder", a.authMiddleware, a.RequirePermission("modules.reorder"), h.ReorderModulesInCourse)
|
||||||
groupV1.Get("/courses/:courseId/modules", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("modules.list_by_course"), h.ListModulesByCourse)
|
groupV1.Get("/courses/:courseId/modules", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("modules.list_by_course"), h.ListModulesByCourse)
|
||||||
|
|
||||||
// /modules/:moduleId/lessons before /modules/:id; /modules/:id/practices before /modules/:id
|
// /modules/:moduleId/lessons before /modules/:id; /modules/:id/practices before /modules/:id
|
||||||
groupV1.Post("/modules/:moduleId/lessons", a.authMiddleware, a.RequirePermission("lessons.create"), h.CreateLesson)
|
groupV1.Post("/modules/:moduleId/lessons", a.authMiddleware, a.RequirePermission("lessons.create"), h.CreateLesson)
|
||||||
groupV1.Get("/modules/:moduleId/lessons", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("lessons.list_by_module"), h.ListLessonsByModule)
|
groupV1.Get("/modules/:moduleId/lessons", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("lessons.list_by_module"), h.ListLessonsByModule)
|
||||||
groupV1.Put("/modules/:moduleId/lessons/reorder", a.authMiddleware, a.RequirePermission("lessons.reorder"), h.ReorderLessonsInModule)
|
groupV1.Put("/modules/:moduleId/lessons/reorder", a.authMiddleware, a.RequirePermission("lessons.reorder"), h.ReorderLessonsInModule)
|
||||||
groupV1.Get("/modules/:id/practices", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("practices.list"), h.ListPracticesByModule)
|
groupV1.Get("/modules/:id/practices", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("practices.list"), h.ListPracticesByModule)
|
||||||
groupV1.Get("/modules/:id", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("modules.get"), h.GetModule)
|
groupV1.Get("/modules/:id", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("modules.get"), h.GetModule)
|
||||||
groupV1.Put("/modules/:id", a.authMiddleware, a.RequirePermission("modules.update"), h.UpdateModule)
|
groupV1.Put("/modules/:id", a.authMiddleware, a.RequirePermission("modules.update"), h.UpdateModule)
|
||||||
groupV1.Delete("/modules/:id", a.authMiddleware, a.RequirePermission("modules.delete"), h.DeleteModule)
|
groupV1.Delete("/modules/:id", a.authMiddleware, a.RequirePermission("modules.delete"), h.DeleteModule)
|
||||||
groupV1.Get("/lessons/:id/practices", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("practices.list"), h.ListPracticesByLesson)
|
groupV1.Get("/lessons/:id/practices", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("practices.list"), h.ListPracticesByLesson)
|
||||||
groupV1.Post("/lessons/:id/complete", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("lessons.complete"), h.CompleteLesson)
|
groupV1.Post("/lessons/:id/complete", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("lessons.complete"), h.CompleteLesson)
|
||||||
groupV1.Post("/videos/engagement/heartbeat", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("videos.track_engagement"), h.RecordVideoEngagementHeartbeat)
|
groupV1.Post("/videos/engagement/heartbeat", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("videos.track_engagement"), h.RecordVideoEngagementHeartbeat)
|
||||||
groupV1.Post("/progress/practices/:id/complete", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("progress.complete"), h.CompletePractice)
|
groupV1.Post("/progress/practices/:id/complete", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("progress.complete"), h.CompletePractice)
|
||||||
groupV1.Get("/lessons/:id", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("lessons.get"), h.GetLesson)
|
groupV1.Get("/lessons/:id", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("lessons.get"), h.GetLesson)
|
||||||
groupV1.Put("/lessons/:id", a.authMiddleware, a.RequirePermission("lessons.update"), h.UpdateLesson)
|
groupV1.Put("/lessons/:id", a.authMiddleware, a.RequirePermission("lessons.update"), h.UpdateLesson)
|
||||||
groupV1.Delete("/lessons/:id", a.authMiddleware, a.RequirePermission("lessons.delete"), h.DeleteLesson)
|
groupV1.Delete("/lessons/:id", a.authMiddleware, a.RequirePermission("lessons.delete"), h.DeleteLesson)
|
||||||
|
|
||||||
groupV1.Post("/practices", a.authMiddleware, a.RequirePermission("practices.create"), h.CreatePractice)
|
groupV1.Post("/practices", a.authMiddleware, a.RequirePermission("practices.create"), h.CreatePractice)
|
||||||
groupV1.Get("/practices/:id", a.authMiddleware, a.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("practices.get"), h.GetPractice)
|
groupV1.Get("/practices/:id", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), a.RequirePermission("practices.get"), h.GetPractice)
|
||||||
groupV1.Put("/practices/:id", a.authMiddleware, a.RequirePermission("practices.update"), h.UpdatePractice)
|
groupV1.Put("/practices/:id", a.authMiddleware, a.RequirePermission("practices.update"), h.UpdatePractice)
|
||||||
groupV1.Put("/practices/:id/full", a.authMiddleware, a.RequirePermission("practices.update"), h.UpdateLmsPracticeFull)
|
groupV1.Put("/practices/:id/full", a.authMiddleware, a.RequirePermission("practices.update"), h.UpdateLmsPracticeFull)
|
||||||
groupV1.Delete("/practices/:id", a.authMiddleware, a.RequirePermission("practices.delete"), h.DeletePractice)
|
groupV1.Delete("/practices/:id", a.authMiddleware, a.RequirePermission("practices.delete"), h.DeletePractice)
|
||||||
|
|
@ -250,7 +250,7 @@ func (a *App) initAppRoutes() {
|
||||||
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/question-types", a.authMiddleware, a.RequirePermission("question_set_items.list"), h.GetQuestionTypesInSet)
|
groupV1.Get("/question-sets/:setId/question-types", a.authMiddleware, a.RequirePermission("question_set_items.list"), h.GetQuestionTypesInSet)
|
||||||
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.RequireSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish), a.RequirePermission("question_set_items.list"), h.GetQuestionsByPractice)
|
groupV1.Get("/practices/:practiceId/questions", a.authMiddleware, a.RequireLMSSubscriptionUnlessFree(), 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