course management service
This commit is contained in:
parent
2c907a34db
commit
8ed0a5f1c6
10
cmd/main.go
10
cmd/main.go
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"Yimaru-Backend/internal/services/arifpay"
|
"Yimaru-Backend/internal/services/arifpay"
|
||||||
"Yimaru-Backend/internal/services/assessment"
|
"Yimaru-Backend/internal/services/assessment"
|
||||||
"Yimaru-Backend/internal/services/authentication"
|
"Yimaru-Backend/internal/services/authentication"
|
||||||
|
"Yimaru-Backend/internal/services/course_management"
|
||||||
issuereporting "Yimaru-Backend/internal/services/issue_reporting"
|
issuereporting "Yimaru-Backend/internal/services/issue_reporting"
|
||||||
"Yimaru-Backend/internal/services/messenger"
|
"Yimaru-Backend/internal/services/messenger"
|
||||||
notificationservice "Yimaru-Backend/internal/services/notification"
|
notificationservice "Yimaru-Backend/internal/services/notification"
|
||||||
|
|
@ -331,6 +332,14 @@ func main() {
|
||||||
cfg,
|
cfg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Course management service
|
||||||
|
courseSvc := course_management.NewService(
|
||||||
|
repository.NewUserStore(store),
|
||||||
|
repository.NewCourseStore(store),
|
||||||
|
notificationSvc,
|
||||||
|
cfg,
|
||||||
|
)
|
||||||
|
|
||||||
arifpaySvc := arifpay.NewArifpayService(cfg, *transactionSvc, &http.Client{
|
arifpaySvc := arifpay.NewArifpayService(cfg, *transactionSvc, &http.Client{
|
||||||
Timeout: 30 * time.Second})
|
Timeout: 30 * time.Second})
|
||||||
|
|
||||||
|
|
@ -342,6 +351,7 @@ func main() {
|
||||||
// Initialize and start HTTP server
|
// Initialize and start HTTP server
|
||||||
app := httpserver.NewApp(
|
app := httpserver.NewApp(
|
||||||
assessmentSvc,
|
assessmentSvc,
|
||||||
|
courseSvc,
|
||||||
arifpaySvc,
|
arifpaySvc,
|
||||||
issueReportingSvc,
|
issueReportingSvc,
|
||||||
cfg.Port,
|
cfg.Port,
|
||||||
|
|
|
||||||
|
|
@ -12,38 +12,8 @@ VALUES
|
||||||
('certificate_enabled', 'true'),
|
('certificate_enabled', 'true'),
|
||||||
('max_courses_per_instructor', '50')
|
('max_courses_per_instructor', '50')
|
||||||
ON CONFLICT (key) DO NOTHING;
|
ON CONFLICT (key) DO NOTHING;
|
||||||
|
-- ======================================================
|
||||||
|
|
||||||
-- ======================================================
|
|
||||||
-- Organizations (Tenants)
|
|
||||||
-- ======================================================
|
|
||||||
INSERT INTO organizations (
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
owner_id,
|
|
||||||
is_active,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
1,
|
|
||||||
'Yimaru Academy',
|
|
||||||
'yimaru-academy',
|
|
||||||
1,
|
|
||||||
TRUE,
|
|
||||||
CURRENT_TIMESTAMP,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
ON CONFLICT (id) DO UPDATE
|
|
||||||
SET name = EXCLUDED.name,
|
|
||||||
slug = EXCLUDED.slug,
|
|
||||||
is_active = EXCLUDED.is_active,
|
|
||||||
updated_at = CURRENT_TIMESTAMP;
|
|
||||||
|
|
||||||
-- ======================================================
|
|
||||||
-- Users
|
|
||||||
-- Roles: SUPER_ADMIN, ORG_ADMIN, INSTRUCTOR, STUDENT
|
|
||||||
-- ======================================================
|
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
id,
|
id,
|
||||||
first_name,
|
first_name,
|
||||||
|
|
@ -51,16 +21,27 @@ INSERT INTO users (
|
||||||
user_name,
|
user_name,
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
password,
|
|
||||||
role,
|
role,
|
||||||
|
password,
|
||||||
age,
|
age,
|
||||||
education_level,
|
education_level,
|
||||||
country,
|
country,
|
||||||
region,
|
region,
|
||||||
|
knowledge_level,
|
||||||
|
nick_name,
|
||||||
|
occupation,
|
||||||
|
learning_goal,
|
||||||
|
language_goal,
|
||||||
|
language_challange,
|
||||||
|
favoutite_topic,
|
||||||
|
initial_assessment_completed,
|
||||||
email_verified,
|
email_verified,
|
||||||
phone_verified,
|
phone_verified,
|
||||||
suspended,
|
status,
|
||||||
organization_id,
|
last_login,
|
||||||
|
profile_completed,
|
||||||
|
profile_picture_url,
|
||||||
|
preferred_language,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
)
|
)
|
||||||
|
|
@ -72,16 +53,27 @@ VALUES
|
||||||
'SarahC',
|
'SarahC',
|
||||||
'yaredyemane1@gmail.com',
|
'yaredyemane1@gmail.com',
|
||||||
NULL,
|
NULL,
|
||||||
crypt('password@123', gen_salt('bf'))::bytea,
|
|
||||||
'SUPER_ADMIN',
|
'SUPER_ADMIN',
|
||||||
|
crypt('password@123', gen_salt('bf'))::bytea,
|
||||||
35,
|
35,
|
||||||
'Masters',
|
'Masters',
|
||||||
'USA',
|
'USA',
|
||||||
'California',
|
'California',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
FALSE,
|
||||||
TRUE,
|
TRUE,
|
||||||
FALSE,
|
FALSE,
|
||||||
|
'ACTIVE',
|
||||||
|
NULL,
|
||||||
FALSE,
|
FALSE,
|
||||||
NULL,
|
NULL,
|
||||||
|
'en',
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
CURRENT_TIMESTAMP
|
CURRENT_TIMESTAMP
|
||||||
),
|
),
|
||||||
|
|
@ -92,16 +84,27 @@ VALUES
|
||||||
'InstructorT',
|
'InstructorT',
|
||||||
'instructor@yimaru.com',
|
'instructor@yimaru.com',
|
||||||
'0988554466',
|
'0988554466',
|
||||||
crypt('password@123', gen_salt('bf'))::bytea,
|
|
||||||
'INSTRUCTOR',
|
'INSTRUCTOR',
|
||||||
|
crypt('password@123', gen_salt('bf'))::bytea,
|
||||||
30,
|
30,
|
||||||
'Bachelors',
|
'Bachelors',
|
||||||
'USA',
|
'USA',
|
||||||
'New York',
|
'New York',
|
||||||
TRUE,
|
NULL,
|
||||||
TRUE,
|
NULL,
|
||||||
|
'Instructor',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
FALSE,
|
FALSE,
|
||||||
1,
|
TRUE,
|
||||||
|
TRUE,
|
||||||
|
'ACTIVE',
|
||||||
|
NULL,
|
||||||
|
FALSE,
|
||||||
|
NULL,
|
||||||
|
'en',
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
CURRENT_TIMESTAMP
|
CURRENT_TIMESTAMP
|
||||||
),
|
),
|
||||||
|
|
@ -112,16 +115,27 @@ VALUES
|
||||||
'DemoS',
|
'DemoS',
|
||||||
'student@yimaru.com',
|
'student@yimaru.com',
|
||||||
NULL,
|
NULL,
|
||||||
crypt('password@123', gen_salt('bf'))::bytea,
|
|
||||||
'STUDENT',
|
'STUDENT',
|
||||||
|
crypt('password@123', gen_salt('bf'))::bytea,
|
||||||
22,
|
22,
|
||||||
'High School',
|
'High School',
|
||||||
'USA',
|
'USA',
|
||||||
'Texas',
|
'Texas',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
FALSE,
|
||||||
TRUE,
|
TRUE,
|
||||||
FALSE,
|
FALSE,
|
||||||
|
'ACTIVE',
|
||||||
|
NULL,
|
||||||
FALSE,
|
FALSE,
|
||||||
1,
|
NULL,
|
||||||
|
'en',
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
CURRENT_TIMESTAMP
|
CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
|
|
@ -131,110 +145,44 @@ SET first_name = EXCLUDED.first_name,
|
||||||
user_name = EXCLUDED.user_name,
|
user_name = EXCLUDED.user_name,
|
||||||
email = EXCLUDED.email,
|
email = EXCLUDED.email,
|
||||||
phone_number = EXCLUDED.phone_number,
|
phone_number = EXCLUDED.phone_number,
|
||||||
password = EXCLUDED.password,
|
|
||||||
role = EXCLUDED.role,
|
role = EXCLUDED.role,
|
||||||
|
password = EXCLUDED.password,
|
||||||
age = EXCLUDED.age,
|
age = EXCLUDED.age,
|
||||||
education_level = EXCLUDED.education_level,
|
education_level = EXCLUDED.education_level,
|
||||||
country = EXCLUDED.country,
|
country = EXCLUDED.country,
|
||||||
region = EXCLUDED.region,
|
region = EXCLUDED.region,
|
||||||
|
knowledge_level = EXCLUDED.knowledge_level,
|
||||||
|
nick_name = EXCLUDED.nick_name,
|
||||||
|
occupation = EXCLUDED.occupation,
|
||||||
|
learning_goal = EXCLUDED.learning_goal,
|
||||||
|
language_goal = EXCLUDED.language_goal,
|
||||||
|
language_challange = EXCLUDED.language_challange,
|
||||||
|
favoutite_topic = EXCLUDED.favoutite_topic,
|
||||||
|
initial_assessment_completed = EXCLUDED.initial_assessment_completed,
|
||||||
email_verified = EXCLUDED.email_verified,
|
email_verified = EXCLUDED.email_verified,
|
||||||
phone_verified = EXCLUDED.phone_verified,
|
phone_verified = EXCLUDED.phone_verified,
|
||||||
suspended = EXCLUDED.suspended,
|
status = EXCLUDED.status,
|
||||||
organization_id = EXCLUDED.organization_id,
|
last_login = EXCLUDED.last_login,
|
||||||
|
profile_completed = EXCLUDED.profile_completed,
|
||||||
|
profile_picture_url = EXCLUDED.profile_picture_url,
|
||||||
|
preferred_language = EXCLUDED.preferred_language,
|
||||||
updated_at = CURRENT_TIMESTAMP;
|
updated_at = CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
-- ======================================================
|
-- ======================================================
|
||||||
-- Courses
|
-- Courses
|
||||||
-- ======================================================
|
-- ======================================================
|
||||||
INSERT INTO courses (
|
|
||||||
id,
|
|
||||||
organization_id,
|
|
||||||
instructor_id,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
level,
|
|
||||||
language,
|
|
||||||
is_published,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
'Introduction to Go Programming',
|
|
||||||
'Learn the fundamentals of Go for backend development.',
|
|
||||||
'beginner',
|
|
||||||
'en',
|
|
||||||
TRUE,
|
|
||||||
CURRENT_TIMESTAMP,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
ON CONFLICT (id) DO UPDATE
|
|
||||||
SET title = EXCLUDED.title,
|
|
||||||
description = EXCLUDED.description,
|
|
||||||
is_published = EXCLUDED.is_published,
|
|
||||||
updated_at = CURRENT_TIMESTAMP;
|
|
||||||
|
|
||||||
-- ======================================================
|
-- ======================================================
|
||||||
-- Course Modules
|
-- Course Categories
|
||||||
-- ======================================================
|
-- ======================================================
|
||||||
INSERT INTO course_modules (
|
INSERT INTO course_categories (
|
||||||
id,
|
id,
|
||||||
course_id,
|
name,
|
||||||
title,
|
is_active,
|
||||||
position,
|
|
||||||
created_at
|
created_at
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES
|
||||||
1,
|
(1, 'Learning English', TRUE, CURRENT_TIMESTAMP),
|
||||||
1,
|
(2, 'Other Courses', TRUE, CURRENT_TIMESTAMP)
|
||||||
'Getting Started',
|
|
||||||
1,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
|
||||||
|
|
||||||
-- ======================================================
|
|
||||||
-- Lessons
|
|
||||||
-- ======================================================
|
|
||||||
INSERT INTO lessons (
|
|
||||||
id,
|
|
||||||
module_id,
|
|
||||||
title,
|
|
||||||
content_type,
|
|
||||||
content_url,
|
|
||||||
duration_minutes,
|
|
||||||
position,
|
|
||||||
created_at
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
'What is Go?',
|
|
||||||
'video',
|
|
||||||
'https://example.com/go-intro',
|
|
||||||
15,
|
|
||||||
1,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
|
||||||
|
|
||||||
-- ======================================================
|
|
||||||
-- Enrollments
|
|
||||||
-- ======================================================
|
|
||||||
INSERT INTO enrollments (
|
|
||||||
id,
|
|
||||||
course_id,
|
|
||||||
student_id,
|
|
||||||
enrolled_at
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
3,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
-- ======================================================
|
-- ======================================================
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,8 @@
|
||||||
-- =========================================
|
|
||||||
-- Notifications
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS global_settings;
|
DROP TABLE IF EXISTS global_settings;
|
||||||
|
|
||||||
-- =========================================
|
|
||||||
-- Notifications
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS notifications;
|
DROP TABLE IF EXISTS notifications;
|
||||||
|
|
||||||
|
|
||||||
-- =========================================
|
|
||||||
-- Issue Reporting
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS reported_issues;
|
DROP TABLE IF EXISTS reported_issues;
|
||||||
|
|
||||||
-- =========================================
|
|
||||||
-- Assessments
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS assessment_submissions;
|
DROP TABLE IF EXISTS assessment_submissions;
|
||||||
DROP TABLE IF EXISTS assessments;
|
DROP TABLE IF EXISTS assessments;
|
||||||
|
|
||||||
-- =========================================
|
|
||||||
-- Progress & Enrollment
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS lesson_progress;
|
|
||||||
DROP TABLE IF EXISTS enrollments;
|
|
||||||
|
|
||||||
-- =========================================
|
|
||||||
-- Course Content Structure
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS lessons;
|
|
||||||
DROP TABLE IF EXISTS course_modules;
|
|
||||||
DROP TABLE IF EXISTS courses;
|
|
||||||
|
|
||||||
|
|
||||||
-- =========================================
|
|
||||||
-- Authentication & Security
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS refresh_tokens;
|
DROP TABLE IF EXISTS refresh_tokens;
|
||||||
DROP TABLE IF EXISTS otps;
|
DROP TABLE IF EXISTS otps;
|
||||||
|
|
||||||
-- =========================================
|
|
||||||
-- Users
|
|
||||||
-- =========================================
|
|
||||||
DROP TABLE IF EXISTS users;
|
DROP TABLE IF EXISTS users;
|
||||||
|
|
@ -95,76 +95,6 @@ CREATE TABLE otps (
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE courses (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
instructor_id BIGINT NOT NULL REFERENCES users(id),
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
level TEXT,
|
|
||||||
language TEXT,
|
|
||||||
is_published BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE course_modules (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
course_id BIGINT NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
position INT NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE lessons (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
module_id BIGINT NOT NULL REFERENCES course_modules(id) ON DELETE CASCADE,
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
content_type TEXT NOT NULL, -- video, article, quiz
|
|
||||||
content_url TEXT,
|
|
||||||
duration_minutes INT,
|
|
||||||
position INT NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE enrollments (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
course_id BIGINT NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
||||||
student_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
enrolled_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
completed_at TIMESTAMPTZ,
|
|
||||||
UNIQUE (course_id, student_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE lesson_progress (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
lesson_id BIGINT NOT NULL REFERENCES lessons(id) ON DELETE CASCADE,
|
|
||||||
student_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
completed BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
completed_at TIMESTAMPTZ,
|
|
||||||
UNIQUE (lesson_id, student_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE assessments (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
course_id BIGINT NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
type TEXT NOT NULL, -- quiz, assignment
|
|
||||||
total_score INT NOT NULL,
|
|
||||||
due_date TIMESTAMPTZ,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE assessment_submissions (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
assessment_id BIGINT NOT NULL REFERENCES assessments(id) ON DELETE CASCADE,
|
|
||||||
student_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
score INT,
|
|
||||||
feedback TEXT,
|
|
||||||
submitted_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
graded_at TIMESTAMPTZ,
|
|
||||||
UNIQUE (assessment_id, student_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS notifications (
|
CREATE TABLE IF NOT EXISTS notifications (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
|
|
||||||
8
db/migrations/000002_courses.down.sql
Normal file
8
db/migrations/000002_courses.down.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
DROP TABLE IF EXISTS course_categories;
|
||||||
|
DROP TABLE IF EXISTS courses;
|
||||||
|
DROP TABLE IF EXISTS programs;
|
||||||
|
DROP TABLE IF EXISTS levels;
|
||||||
|
DROP TABLE IF EXISTS modules;
|
||||||
|
DROP TABLE IF EXISTS module_videos;
|
||||||
|
DROP TABLE IF EXISTS practices;
|
||||||
|
DROP TABLE IF EXISTS practice_questions;
|
||||||
103
db/migrations/000002_courses.up.sql
Normal file
103
db/migrations/000002_courses.up.sql
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS course_categories (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(150) NOT NULL, -- "Learning English", "Other Courses"
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS courses (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
category_id BIGINT NOT NULL REFERENCES course_categories(id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS programs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
course_id BIGINT NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
thumbnail TEXT,
|
||||||
|
display_order INT NOT NULL DEFAULT 0,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS levels (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
program_id BIGINT NOT NULL REFERENCES programs(id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
level_index INT NOT NULL, -- 1,2,3...
|
||||||
|
|
||||||
|
number_of_modules INT NOT NULL DEFAULT 0,
|
||||||
|
number_of_practices INT NOT NULL DEFAULT 0,
|
||||||
|
number_of_videos INT NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS modules (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
level_id BIGINT NOT NULL REFERENCES levels(id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
content TEXT,
|
||||||
|
display_order INT NOT NULL DEFAULT 0,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS module_videos (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
module_id BIGINT NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
video_url TEXT NOT NULL,
|
||||||
|
duration INT NOT NULL, -- seconds
|
||||||
|
resolution VARCHAR(20), -- "720p", "1080p"
|
||||||
|
|
||||||
|
is_published BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
publish_date TIMESTAMPTZ,
|
||||||
|
visibility VARCHAR(50), -- public, private, unlisted
|
||||||
|
|
||||||
|
instructor_id VARCHAR(100),
|
||||||
|
thumbnail TEXT,
|
||||||
|
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS practices (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
owner_type VARCHAR(50) NOT NULL, -- LEVEL | MODULE
|
||||||
|
owner_id BIGINT NOT NULL,
|
||||||
|
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
banner_image TEXT,
|
||||||
|
persona VARCHAR(100),
|
||||||
|
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
|
||||||
|
CHECK (owner_type IN ('LEVEL', 'MODULE'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS practice_questions (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
practice_id BIGINT NOT NULL REFERENCES practices(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
question TEXT NOT NULL,
|
||||||
|
question_voice_prompt TEXT,
|
||||||
|
sample_answer_voice_prompt TEXT,
|
||||||
|
sample_answer TEXT,
|
||||||
|
tips TEXT,
|
||||||
|
|
||||||
|
type VARCHAR(50) NOT NULL -- MCQ, TRUE_FALSE, SHORT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_courses_category_id ON courses(category_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_programs_course_id ON programs(course_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_levels_program_id ON levels(program_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_modules_level_id ON modules(level_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_videos_module_id ON module_videos(module_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_practices_owner ON practices(owner_type, owner_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_practice_questions_practice_id ON practice_questions(practice_id);
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
-- -- name: CreateBranch :one
|
|
||||||
-- INSERT INTO branches (
|
|
||||||
-- name,
|
|
||||||
-- location,
|
|
||||||
-- wallet_id,
|
|
||||||
-- branch_manager_id,
|
|
||||||
-- company_id,
|
|
||||||
-- is_self_owned,
|
|
||||||
-- profit_percent
|
|
||||||
-- )
|
|
||||||
-- VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: CreateSupportedOperation :one
|
|
||||||
-- INSERT INTO supported_operations (name, description)
|
|
||||||
-- VALUES ($1, $2)
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: CreateBranchOperation :one
|
|
||||||
-- INSERT INTO branch_operations (operation_id, branch_id)
|
|
||||||
-- VALUES ($1, $2)
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: CreateBranchCashier :one
|
|
||||||
-- INSERT INTO branch_cashiers (user_id, branch_id)
|
|
||||||
-- VALUES ($1, $2)
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: GetAllBranches :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM branch_details
|
|
||||||
-- WHERE (
|
|
||||||
-- company_id = sqlc.narg('company_id')
|
|
||||||
-- OR sqlc.narg('company_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- is_active = sqlc.narg('is_active')
|
|
||||||
-- OR sqlc.narg('is_active') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- branch_manager_id = sqlc.narg('branch_manager_id')
|
|
||||||
-- OR sqlc.narg('branch_manager_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- name ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR location ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR sqlc.narg('query') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- created_at > sqlc.narg('created_before')
|
|
||||||
-- OR sqlc.narg('created_before') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- created_at < sqlc.narg('created_after')
|
|
||||||
-- OR sqlc.narg('created_after') IS NULL
|
|
||||||
-- );
|
|
||||||
-- -- name: GetBranchByID :one
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM branch_details
|
|
||||||
-- WHERE id = $1;
|
|
||||||
-- -- name: GetBranchByCompanyID :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM branch_details
|
|
||||||
-- WHERE company_id = $1;
|
|
||||||
-- -- name: GetBranchByManagerID :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM branch_details
|
|
||||||
-- WHERE branch_manager_id = $1;
|
|
||||||
-- -- name: SearchBranchByName :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM branch_details
|
|
||||||
-- WHERE name ILIKE '%' || $1 || '%'
|
|
||||||
-- AND (
|
|
||||||
-- company_id = sqlc.narg('company_id')
|
|
||||||
-- OR sqlc.narg('company_id') IS NULL
|
|
||||||
-- );
|
|
||||||
-- -- name: GetAllSupportedOperations :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM supported_operations;
|
|
||||||
-- -- name: GetBranchOperations :many
|
|
||||||
-- SELECT branch_operations.*,
|
|
||||||
-- supported_operations.name,
|
|
||||||
-- supported_operations.description
|
|
||||||
-- FROM branch_operations
|
|
||||||
-- JOIN supported_operations ON branch_operations.operation_id = supported_operations.id
|
|
||||||
-- WHERE branch_operations.branch_id = $1;
|
|
||||||
-- -- name: GetBranchByCashier :one
|
|
||||||
-- SELECT branches.*
|
|
||||||
-- FROM branch_cashiers
|
|
||||||
-- JOIN branches ON branch_cashiers.branch_id = branches.id
|
|
||||||
-- WHERE branch_cashiers.user_id = $1;
|
|
||||||
-- -- name: UpdateBranch :one
|
|
||||||
-- UPDATE branches
|
|
||||||
-- SET name = COALESCE(sqlc.narg(name), name),
|
|
||||||
-- location = COALESCE(sqlc.narg(location), location),
|
|
||||||
-- branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_id),
|
|
||||||
-- company_id = COALESCE(sqlc.narg(company_id), company_id),
|
|
||||||
-- is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned),
|
|
||||||
-- is_active = COALESCE(sqlc.narg(is_active), is_active),
|
|
||||||
-- profit_percent = COALESCE(sqlc.narg(profit_percent), profit_percent),
|
|
||||||
-- updated_at = CURRENT_TIMESTAMP
|
|
||||||
-- WHERE id = $1
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: DeleteBranch :exec
|
|
||||||
-- DELETE FROM branches
|
|
||||||
-- WHERE id = $1;
|
|
||||||
-- -- name: DeleteBranchOperation :exec
|
|
||||||
-- DELETE FROM branch_operations
|
|
||||||
-- WHERE operation_id = $1
|
|
||||||
-- AND branch_id = $2;
|
|
||||||
-- -- name: DeleteBranchCashier :exec
|
|
||||||
-- DELETE FROM branch_cashiers
|
|
||||||
-- WHERE user_id = $1;
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
-- -- name: UpdateBranchStats :exec
|
|
||||||
-- WITH -- Aggregate bet data per branch
|
|
||||||
-- bet_stats AS (
|
|
||||||
-- SELECT branch_id,
|
|
||||||
-- COUNT(*) AS total_bets,
|
|
||||||
-- COALESCE(SUM(amount), 0) AS total_stake,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(amount) * MAX(profit_percent),
|
|
||||||
-- 0
|
|
||||||
-- ) AS deducted_stake,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(
|
|
||||||
-- CASE
|
|
||||||
-- WHEN cashed_out THEN amount
|
|
||||||
-- ELSE 0
|
|
||||||
-- END
|
|
||||||
-- ),
|
|
||||||
-- 0
|
|
||||||
-- ) AS total_cash_out,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(
|
|
||||||
-- CASE
|
|
||||||
-- WHEN status = 3 THEN amount
|
|
||||||
-- ELSE 0
|
|
||||||
-- END
|
|
||||||
-- ),
|
|
||||||
-- 0
|
|
||||||
-- ) AS total_cash_backs,
|
|
||||||
-- COUNT(*) FILTER (
|
|
||||||
-- WHERE status = 5
|
|
||||||
-- ) AS number_of_unsettled,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(
|
|
||||||
-- CASE
|
|
||||||
-- WHEN status = 5 THEN amount
|
|
||||||
-- ELSE 0
|
|
||||||
-- END
|
|
||||||
-- ),
|
|
||||||
-- 0
|
|
||||||
-- ) AS total_unsettled_amount
|
|
||||||
-- FROM shop_bet_detail
|
|
||||||
-- LEFT JOIN branches ON branches.id = shop_bet_detail.branch_id
|
|
||||||
-- GROUP BY branch_id
|
|
||||||
-- ),
|
|
||||||
-- cashier_stats AS (
|
|
||||||
-- SELECT branch_id,
|
|
||||||
-- COUNT(*) AS total_cashiers
|
|
||||||
-- FROM branch_cashiers
|
|
||||||
-- GROUP BY branch_id
|
|
||||||
-- )
|
|
||||||
-- INSERT INTO branch_stats (
|
|
||||||
-- branch_id,
|
|
||||||
-- branch_name,
|
|
||||||
-- company_id,
|
|
||||||
-- company_name,
|
|
||||||
-- company_slug,
|
|
||||||
-- interval_start,
|
|
||||||
-- total_bets,
|
|
||||||
-- total_stake,
|
|
||||||
-- deducted_stake,
|
|
||||||
-- total_cash_out,
|
|
||||||
-- total_cash_backs,
|
|
||||||
-- number_of_unsettled,
|
|
||||||
-- total_unsettled_amount,
|
|
||||||
-- total_cashiers,
|
|
||||||
-- updated_at
|
|
||||||
-- )
|
|
||||||
-- SELECT br.id AS branch_id,
|
|
||||||
-- br.name AS branch_name,
|
|
||||||
-- c.id AS company_id,
|
|
||||||
-- c.name AS company_name,
|
|
||||||
-- c.slug AS company_slug,
|
|
||||||
-- DATE_TRUNC('day', NOW() AT TIME ZONE 'UTC') AS interval_start,
|
|
||||||
-- COALESCE(bs.total_bets, 0) AS total_bets,
|
|
||||||
-- COALESCE(bs.total_stake, 0) AS total_stake,
|
|
||||||
-- COALESCE(bs.deducted_stake, 0) AS deducted_stake,
|
|
||||||
-- COALESCE(bs.total_cash_out, 0) AS total_cash_out,
|
|
||||||
-- COALESCE(bs.total_cash_backs, 0) AS total_cash_backs,
|
|
||||||
-- COALESCE(bs.number_of_unsettled, 0) AS number_of_unsettled,
|
|
||||||
-- COALESCE(bs.total_unsettled_amount, 0) AS total_unsettled_amount,
|
|
||||||
-- COALESCE(bc.total_cashiers, 0) AS total_cashiers,
|
|
||||||
-- NOW() AS updated_at
|
|
||||||
-- FROM branches br
|
|
||||||
-- LEFT JOIN companies c ON c.id = br.company_id
|
|
||||||
-- LEFT JOIN bet_stats bs ON bs.branch_id = br.id
|
|
||||||
-- LEFT JOIN cashier_stats bc ON bc.branch_id = br.id ON CONFLICT (branch_id, interval_start) DO
|
|
||||||
-- UPDATE
|
|
||||||
-- SET total_bets = EXCLUDED.total_bets,
|
|
||||||
-- total_stake = EXCLUDED.total_stake,
|
|
||||||
-- deducted_stake = EXCLUDED.deducted_stake,
|
|
||||||
-- total_cash_out = EXCLUDED.total_cash_out,
|
|
||||||
-- total_cash_backs = EXCLUDED.total_cash_backs,
|
|
||||||
-- number_of_unsettled = EXCLUDED.number_of_unsettled,
|
|
||||||
-- total_unsettled_amount = EXCLUDED.total_unsettled_amount,
|
|
||||||
-- total_cashiers = EXCLUDED.total_cashiers,
|
|
||||||
-- updated_at = EXCLUDED.updated_at;
|
|
||||||
-- -- name: GetBranchStatsByID :many
|
|
||||||
-- SELECt *
|
|
||||||
-- FROM branch_stats
|
|
||||||
-- WHERE branch_id = $1
|
|
||||||
-- ORDER BY interval_start DESC;
|
|
||||||
-- -- name: GetBranchStats :many
|
|
||||||
-- SELECT DATE_TRUNC(sqlc.narg('interval'), interval_start)::timestamp AS interval_start,
|
|
||||||
-- branch_stats.branch_id,
|
|
||||||
-- branch_stats.branch_name,
|
|
||||||
-- branch_stats.company_id,
|
|
||||||
-- branch_stats.company_name,
|
|
||||||
-- branch_stats.company_slug,
|
|
||||||
-- branch_stats.total_bets,
|
|
||||||
-- branch_stats.total_stake,
|
|
||||||
-- branch_stats.deducted_stake,
|
|
||||||
-- branch_stats.total_cash_out,
|
|
||||||
-- branch_stats.total_cash_backs,
|
|
||||||
-- branch_stats.number_of_unsettled,
|
|
||||||
-- branch_stats.total_unsettled_amount,
|
|
||||||
-- branch_stats.total_cashiers,
|
|
||||||
-- branch_stats.updated_at
|
|
||||||
-- FROM branch_stats
|
|
||||||
-- WHERE (
|
|
||||||
-- branch_stats.branch_id = sqlc.narg('branch_id')
|
|
||||||
-- OR sqlc.narg('branch_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- branch_stats.company_id = sqlc.narg('company_id')
|
|
||||||
-- OR sqlc.narg('company_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- GROUP BY interval_start
|
|
||||||
-- ORDER BY interval_start DESC;
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
-- -- name: CreateCompany :one
|
|
||||||
-- INSERT INTO companies (
|
|
||||||
-- name,
|
|
||||||
-- slug,
|
|
||||||
-- admin_id,
|
|
||||||
-- wallet_id,
|
|
||||||
-- deducted_percentage,
|
|
||||||
-- is_active
|
|
||||||
-- )
|
|
||||||
-- VALUES ($1, $2, $3, $4, $5, $6)
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: GetAllCompanies :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM companies_details
|
|
||||||
-- WHERE (
|
|
||||||
-- name ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR admin_first_name ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR admin_last_name ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR admin_phone_number ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR sqlc.narg('query') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- created_at > sqlc.narg('created_before')
|
|
||||||
-- OR sqlc.narg('created_before') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- created_at < sqlc.narg('created_after')
|
|
||||||
-- OR sqlc.narg('created_after') IS NULL
|
|
||||||
-- );
|
|
||||||
-- -- name: GetCompanyByID :one
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM companies_details
|
|
||||||
-- WHERE id = $1;
|
|
||||||
-- -- name: GetCompanyUsingSlug :one
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM companies
|
|
||||||
-- WHERE slug = $1;
|
|
||||||
-- -- name: SearchCompanyByName :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM companies_details
|
|
||||||
-- WHERE name ILIKE '%' || $1 || '%';
|
|
||||||
-- -- name: UpdateCompany :exec
|
|
||||||
-- UPDATE companies
|
|
||||||
-- SET name = COALESCE(sqlc.narg(name), name),
|
|
||||||
-- admin_id = COALESCE(sqlc.narg(admin_id), admin_id),
|
|
||||||
-- is_active = COALESCE(sqlc.narg(is_active), is_active),
|
|
||||||
-- deducted_percentage = COALESCE(
|
|
||||||
-- sqlc.narg(deducted_percentage),
|
|
||||||
-- deducted_percentage
|
|
||||||
-- ),
|
|
||||||
-- slug = COALESCE(sqlc.narg(slug), slug),
|
|
||||||
-- updated_at = CURRENT_TIMESTAMP
|
|
||||||
-- WHERE id = $1;
|
|
||||||
-- -- name: DeleteCompany :exec
|
|
||||||
-- DELETE FROM companies
|
|
||||||
-- WHERE id = $1;
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
-- -- name: UpdateCompanyStats :exec
|
|
||||||
-- WITH -- Aggregate bet data per company
|
|
||||||
-- bet_stats AS (
|
|
||||||
-- SELECT company_id,
|
|
||||||
-- COUNT(*) AS total_bets,
|
|
||||||
-- COALESCE(SUM(amount), 0) AS total_stake,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(amount) * MAX(companies.deducted_percentage),
|
|
||||||
-- 0
|
|
||||||
-- ) AS deducted_stake,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(
|
|
||||||
-- CASE
|
|
||||||
-- WHEN cashed_out THEN amount
|
|
||||||
-- ELSE 0
|
|
||||||
-- END
|
|
||||||
-- ),
|
|
||||||
-- 0
|
|
||||||
-- ) AS total_cash_out,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(
|
|
||||||
-- CASE
|
|
||||||
-- WHEN status = 3 THEN amount
|
|
||||||
-- ELSE 0
|
|
||||||
-- END
|
|
||||||
-- ),
|
|
||||||
-- 0
|
|
||||||
-- ) AS total_cash_backs,
|
|
||||||
-- COUNT(*) FILTER (
|
|
||||||
-- WHERE status = 5
|
|
||||||
-- ) AS number_of_unsettled,
|
|
||||||
-- COALESCE(
|
|
||||||
-- SUM(
|
|
||||||
-- CASE
|
|
||||||
-- WHEN status = 5 THEN amount
|
|
||||||
-- ELSE 0
|
|
||||||
-- END
|
|
||||||
-- ),
|
|
||||||
-- 0
|
|
||||||
-- ) AS total_unsettled_amount
|
|
||||||
-- FROM shop_bet_detail
|
|
||||||
-- LEFT JOIN companies ON companies.id = shop_bet_detail.company_id
|
|
||||||
-- GROUP BY company_id
|
|
||||||
-- ),
|
|
||||||
-- -- Aggregate user counts per company
|
|
||||||
-- user_stats AS (
|
|
||||||
-- SELECT company_id,
|
|
||||||
-- COUNT(*) FILTER (
|
|
||||||
-- WHERE role = 'admin'
|
|
||||||
-- ) AS total_admins,
|
|
||||||
-- COUNT(*) FILTER (
|
|
||||||
-- WHERE role = 'branch_manager'
|
|
||||||
-- ) AS total_managers,
|
|
||||||
-- COUNT(*) FILTER (
|
|
||||||
-- WHERE role = 'cashier'
|
|
||||||
-- ) AS total_cashiers,
|
|
||||||
-- COUNT(*) FILTER (
|
|
||||||
-- WHERE role = 'customer'
|
|
||||||
-- ) AS total_customers,
|
|
||||||
-- COUNT(*) FILTER (
|
|
||||||
-- WHERE role = 'transaction_approver'
|
|
||||||
-- ) AS total_approvers
|
|
||||||
-- FROM users
|
|
||||||
-- GROUP BY company_id
|
|
||||||
-- ),
|
|
||||||
-- -- Aggregate branch counts per company
|
|
||||||
-- branch_stats AS (
|
|
||||||
-- SELECT company_id,
|
|
||||||
-- COUNT(*) AS total_branches
|
|
||||||
-- FROM branches
|
|
||||||
-- GROUP BY company_id
|
|
||||||
-- ) -- Final combined aggregation
|
|
||||||
-- INSERT INTO company_stats (
|
|
||||||
-- company_id,
|
|
||||||
-- company_name,
|
|
||||||
-- company_slug,
|
|
||||||
-- interval_start,
|
|
||||||
-- total_bets,
|
|
||||||
-- total_stake,
|
|
||||||
-- deducted_stake,
|
|
||||||
-- total_cash_out,
|
|
||||||
-- total_cash_backs,
|
|
||||||
-- number_of_unsettled,
|
|
||||||
-- total_unsettled_amount,
|
|
||||||
-- total_admins,
|
|
||||||
-- total_managers,
|
|
||||||
-- total_cashiers,
|
|
||||||
-- total_customers,
|
|
||||||
-- total_approvers,
|
|
||||||
-- total_branches,
|
|
||||||
-- updated_at
|
|
||||||
-- )
|
|
||||||
-- SELECT c.id AS company_id,
|
|
||||||
-- c.name AS company_name,
|
|
||||||
-- c.slug AS company_slug,
|
|
||||||
-- DATE_TRUNC('day', NOW() AT TIME ZONE 'UTC') AS interval_start,
|
|
||||||
-- COALESCE(b.total_bets, 0) AS total_bets,
|
|
||||||
-- COALESCE(b.total_stake, 0) AS total_stake,
|
|
||||||
-- COALESCE(b.deducted_stake, 0) AS deducted_stake,
|
|
||||||
-- COALESCE(b.total_cash_out, 0) AS total_cash_out,
|
|
||||||
-- COALESCE(b.total_cash_backs, 0) AS total_cash_backs,
|
|
||||||
-- COALESCE(b.number_of_unsettled, 0) AS number_of_unsettled,
|
|
||||||
-- COALESCE(b.total_unsettled_amount, 0) AS total_unsettled_amount,
|
|
||||||
-- COALESCE(u.total_admins, 0) AS total_admins,
|
|
||||||
-- COALESCE(u.total_managers, 0) AS total_managers,
|
|
||||||
-- COALESCE(u.total_cashiers, 0) AS total_cashiers,
|
|
||||||
-- COALESCE(u.total_customers, 0) AS total_customers,
|
|
||||||
-- COALESCE(u.total_approvers, 0) AS total_approvers,
|
|
||||||
-- COALESCE(br.total_branches, 0) AS total_branches,
|
|
||||||
-- NOW() AS updated_at
|
|
||||||
-- FROM companies c
|
|
||||||
-- LEFT JOIN bet_stats b ON b.company_id = c.id
|
|
||||||
-- LEFT JOIN user_stats u ON u.company_id = c.id
|
|
||||||
-- LEFT JOIN branch_stats br ON br.company_id = c.id ON CONFLICT (company_id, interval_start) DO
|
|
||||||
-- UPDATE
|
|
||||||
-- SET total_bets = EXCLUDED.total_bets,
|
|
||||||
-- total_stake = EXCLUDED.total_stake,
|
|
||||||
-- deducted_stake = EXCLUDED.deducted_stake,
|
|
||||||
-- total_cash_out = EXCLUDED.total_cash_out,
|
|
||||||
-- total_cash_backs = EXCLUDED.total_cash_backs,
|
|
||||||
-- number_of_unsettled = EXCLUDED.number_of_unsettled,
|
|
||||||
-- total_unsettled_amount = EXCLUDED.total_unsettled_amount,
|
|
||||||
-- total_admins = EXCLUDED.total_admins,
|
|
||||||
-- total_managers = EXCLUDED.total_managers,
|
|
||||||
-- total_cashiers = EXCLUDED.total_cashiers,
|
|
||||||
-- total_customers = EXCLUDED.total_customers,
|
|
||||||
-- total_approvers = EXCLUDED.total_approvers,
|
|
||||||
-- total_branches = EXCLUDED.total_branches,
|
|
||||||
-- updated_at = EXCLUDED.updated_at;
|
|
||||||
-- -- name: GetCompanyStatsByID :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM company_stats
|
|
||||||
-- WHERE company_id = $1
|
|
||||||
-- ORDER BY interval_start DESC;
|
|
||||||
-- -- name: GetCompanyStats :many
|
|
||||||
-- SELECT DATE_TRUNC(sqlc.narg('interval'), interval_start)::timestamp AS interval_start,
|
|
||||||
-- company_stats.company_id,
|
|
||||||
-- company_stats.company_name,
|
|
||||||
-- company_stats.company_slug,
|
|
||||||
-- company_stats.total_bets,
|
|
||||||
-- company_stats.total_stake,
|
|
||||||
-- company_stats.deducted_stake,
|
|
||||||
-- company_stats.total_cash_out,
|
|
||||||
-- company_stats.total_cash_backs,
|
|
||||||
-- company_stats.number_of_unsettled,
|
|
||||||
-- company_stats.total_unsettled_amount,
|
|
||||||
-- company_stats.total_admins,
|
|
||||||
-- company_stats.total_managers,
|
|
||||||
-- company_stats.total_cashiers,
|
|
||||||
-- company_stats.total_customers,
|
|
||||||
-- company_stats.total_approvers,
|
|
||||||
-- company_stats.total_branches,
|
|
||||||
-- company_stats.updated_at
|
|
||||||
-- FROM company_stats
|
|
||||||
-- WHERE (
|
|
||||||
-- company_stats.company_id = sqlc.narg('company_id')
|
|
||||||
-- OR sqlc.narg('company_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- GROUP BY interval_start
|
|
||||||
-- ORDER BY interval_start DESC;
|
|
||||||
52
db/query/course_catagories.sql
Normal file
52
db/query/course_catagories.sql
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
-- name: CreateCourseCategory :one
|
||||||
|
INSERT INTO course_categories (
|
||||||
|
name,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- name
|
||||||
|
$2 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at;
|
||||||
|
|
||||||
|
-- name: GetCourseCategoryByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at
|
||||||
|
FROM course_categories
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListActiveCourseCategories :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at
|
||||||
|
FROM course_categories
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: UpdateCourseCategory :one
|
||||||
|
UPDATE course_categories
|
||||||
|
SET
|
||||||
|
name = $2,
|
||||||
|
is_active = $3
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at;
|
||||||
|
|
||||||
|
-- name: DeactivateCourseCategory :exec
|
||||||
|
UPDATE course_categories
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
|
||||||
88
db/query/course_programs.sql
Normal file
88
db/query/course_programs.sql
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
-- name: CreateProgram :one
|
||||||
|
INSERT INTO programs (
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- course_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4, -- thumbnail
|
||||||
|
$5, -- display_order
|
||||||
|
$6 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: GetProgramByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM programs
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListProgramsByCourse :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM programs
|
||||||
|
WHERE course_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY display_order ASC, id ASC;
|
||||||
|
|
||||||
|
-- name: ListActivePrograms :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM programs
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
ORDER BY display_order ASC;
|
||||||
|
|
||||||
|
-- name: UpdateProgram :one
|
||||||
|
UPDATE programs
|
||||||
|
SET
|
||||||
|
course_id = $2,
|
||||||
|
title = $3,
|
||||||
|
description = $4,
|
||||||
|
thumbnail = $5,
|
||||||
|
display_order = $6,
|
||||||
|
is_active = $7
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: DeactivateProgram :exec
|
||||||
|
UPDATE programs
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1;
|
||||||
73
db/query/courses.sql
Normal file
73
db/query/courses.sql
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
-- name: CreateCourse :one
|
||||||
|
INSERT INTO courses (
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- category_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: GetCourseByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
FROM courses
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListCoursesByCategory :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
FROM courses
|
||||||
|
WHERE category_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY id DESC;
|
||||||
|
|
||||||
|
-- name: ListActiveCourses :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
FROM courses
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
ORDER BY id DESC;
|
||||||
|
|
||||||
|
-- name: UpdateCourse :one
|
||||||
|
UPDATE courses
|
||||||
|
SET
|
||||||
|
category_id = $2,
|
||||||
|
title = $3,
|
||||||
|
description = $4,
|
||||||
|
is_active = $5
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: DeactivateCourse :exec
|
||||||
|
UPDATE courses
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
-- -- name: CreateFlag :one
|
|
||||||
-- INSERT INTO flags (
|
|
||||||
-- bet_id,
|
|
||||||
-- odds_market_id,
|
|
||||||
-- reason
|
|
||||||
-- ) VALUES (
|
|
||||||
-- $1, $2, $3
|
|
||||||
-- ) RETURNING *;
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
-- -- name: CreateBank :one
|
|
||||||
-- INSERT INTO banks (
|
|
||||||
-- slug,
|
|
||||||
-- swift,
|
|
||||||
-- name,
|
|
||||||
-- acct_length,
|
|
||||||
-- country_id,
|
|
||||||
-- is_mobilemoney,
|
|
||||||
-- is_active,
|
|
||||||
-- is_rtgs,
|
|
||||||
-- active,
|
|
||||||
-- is_24hrs,
|
|
||||||
-- created_at,
|
|
||||||
-- updated_at,
|
|
||||||
-- currency,
|
|
||||||
-- bank_logo
|
|
||||||
-- )
|
|
||||||
-- VALUES (
|
|
||||||
-- $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, $11, $12
|
|
||||||
-- )
|
|
||||||
-- RETURNING *;
|
|
||||||
|
|
||||||
-- -- name: GetBankByID :one
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM banks
|
|
||||||
-- WHERE id = $1;
|
|
||||||
|
|
||||||
-- -- name: GetAllBanks :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM banks
|
|
||||||
-- WHERE (
|
|
||||||
-- country_id = sqlc.narg('country_id')
|
|
||||||
-- OR sqlc.narg('country_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- is_active = sqlc.narg('is_active')
|
|
||||||
-- OR sqlc.narg('is_active') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- name ILIKE '%' || sqlc.narg('search_term') || '%'
|
|
||||||
-- OR sqlc.narg('search_term') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- code ILIKE '%' || sqlc.narg('search_term') || '%'
|
|
||||||
-- OR sqlc.narg('search_term') IS NULL
|
|
||||||
-- )
|
|
||||||
-- ORDER BY name ASC
|
|
||||||
-- LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
|
||||||
|
|
||||||
-- -- name: CountBanks :one
|
|
||||||
-- SELECT COUNT(*)
|
|
||||||
-- FROM banks
|
|
||||||
-- WHERE (
|
|
||||||
-- country_id = $1
|
|
||||||
-- OR $1 IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- is_active = $2
|
|
||||||
-- OR $2 IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- name ILIKE '%' || $3 || '%'
|
|
||||||
-- OR code ILIKE '%' || $3 || '%'
|
|
||||||
-- OR $3 IS NULL
|
|
||||||
-- );
|
|
||||||
|
|
||||||
-- -- name: UpdateBank :one
|
|
||||||
-- UPDATE banks
|
|
||||||
-- SET slug = COALESCE(sqlc.narg(slug), slug),
|
|
||||||
-- swift = COALESCE(sqlc.narg(swift), swift),
|
|
||||||
-- name = COALESCE(sqlc.narg(name), name),
|
|
||||||
-- acct_length = COALESCE(sqlc.narg(acct_length), acct_length),
|
|
||||||
-- country_id = COALESCE(sqlc.narg(country_id), country_id),
|
|
||||||
-- is_mobilemoney = COALESCE(sqlc.narg(is_mobilemoney), is_mobilemoney),
|
|
||||||
-- is_active = COALESCE(sqlc.narg(is_active), is_active),
|
|
||||||
-- is_rtgs = COALESCE(sqlc.narg(is_rtgs), is_rtgs),
|
|
||||||
-- active = COALESCE(sqlc.narg(active), active),
|
|
||||||
-- is_24hrs = COALESCE(sqlc.narg(is_24hrs), is_24hrs),
|
|
||||||
-- updated_at = CURRENT_TIMESTAMP,
|
|
||||||
-- currency = COALESCE(sqlc.narg(currency), currency),
|
|
||||||
-- bank_logo = COALESCE(sqlc.narg(bank_logo), bank_logo)
|
|
||||||
-- WHERE id = $1
|
|
||||||
-- RETURNING *;
|
|
||||||
|
|
||||||
-- -- name: DeleteBank :exec
|
|
||||||
-- DELETE FROM banks
|
|
||||||
-- WHERE id = $1;
|
|
||||||
67
db/query/level_modules.sql
Normal file
67
db/query/level_modules.sql
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
-- name: CreateModule :one
|
||||||
|
INSERT INTO modules (
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- level_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- content
|
||||||
|
$4, -- display_order
|
||||||
|
$5 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: GetModuleByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM modules
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListModulesByLevel :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM modules
|
||||||
|
WHERE level_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY display_order ASC, id ASC;
|
||||||
|
|
||||||
|
-- name: UpdateModule :one
|
||||||
|
UPDATE modules
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
content = $3,
|
||||||
|
display_order = $4,
|
||||||
|
is_active = $5
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: DeactivateModule :exec
|
||||||
|
UPDATE modules
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1;
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
-- -- name: GetAllBranchLocations :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM branch_locations
|
|
||||||
-- WHERE (
|
|
||||||
-- value ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR sqlc.narg('query') IS NULL
|
|
||||||
-- );
|
|
||||||
141
db/query/module_videos.sql
Normal file
141
db/query/module_videos.sql
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
-- name: CreateModuleVideo :one
|
||||||
|
INSERT INTO module_videos (
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- module_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4, -- video_url
|
||||||
|
$5, -- duration
|
||||||
|
$6, -- resolution
|
||||||
|
|
||||||
|
$7, -- is_published
|
||||||
|
$8, -- publish_date
|
||||||
|
$9, -- visibility
|
||||||
|
|
||||||
|
$10, -- instructor_id
|
||||||
|
$11, -- thumbnail
|
||||||
|
$12 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: GetModuleVideoByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
FROM module_videos
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListPublishedVideosByModule :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail
|
||||||
|
FROM module_videos
|
||||||
|
WHERE module_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
AND is_published = TRUE
|
||||||
|
ORDER BY publish_date ASC, id ASC;
|
||||||
|
|
||||||
|
-- name: ListAllVideosByModule :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
FROM module_videos
|
||||||
|
WHERE module_id = $1
|
||||||
|
ORDER BY id ASC;
|
||||||
|
|
||||||
|
-- name: UpdateModuleVideo :one
|
||||||
|
UPDATE module_videos
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
description = $3,
|
||||||
|
video_url = $4,
|
||||||
|
duration = $5,
|
||||||
|
resolution = $6,
|
||||||
|
|
||||||
|
is_published = $7,
|
||||||
|
publish_date = $8,
|
||||||
|
visibility = $9,
|
||||||
|
|
||||||
|
instructor_id = $10,
|
||||||
|
thumbnail = $11,
|
||||||
|
is_active = $12
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: DeactivateModuleVideo :exec
|
||||||
|
UPDATE module_videos
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
79
db/query/practice_questions.sql
Normal file
79
db/query/practice_questions.sql
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
-- name: CreatePracticeQuestion :one
|
||||||
|
INSERT INTO practice_questions (
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- practice_id
|
||||||
|
$2, -- question
|
||||||
|
$3, -- question_voice_prompt
|
||||||
|
$4, -- sample_answer_voice_prompt
|
||||||
|
$5, -- sample_answer
|
||||||
|
$6, -- tips
|
||||||
|
$7 -- type (MCQ, TRUE_FALSE, SHORT)
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type;
|
||||||
|
|
||||||
|
-- name: GetPracticeQuestionByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
FROM practice_questions
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListPracticeQuestions :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
FROM practice_questions
|
||||||
|
WHERE practice_id = $1
|
||||||
|
ORDER BY id ASC;
|
||||||
|
|
||||||
|
-- name: UpdatePracticeQuestion :one
|
||||||
|
UPDATE practice_questions
|
||||||
|
SET
|
||||||
|
question = $2,
|
||||||
|
question_voice_prompt = $3,
|
||||||
|
sample_answer_voice_prompt = $4,
|
||||||
|
sample_answer = $5,
|
||||||
|
tips = $6,
|
||||||
|
type = $7
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type;
|
||||||
|
|
||||||
|
-- name: DeletePracticeQuestion :exec
|
||||||
|
DELETE FROM practice_questions
|
||||||
|
WHERE id = $1;
|
||||||
81
db/query/practices.sql
Normal file
81
db/query/practices.sql
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
-- name: CreatePractice :one
|
||||||
|
INSERT INTO practices (
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- owner_type (LEVEL | MODULE)
|
||||||
|
$2, -- owner_id
|
||||||
|
$3, -- title
|
||||||
|
$4, -- description
|
||||||
|
$5, -- banner_image
|
||||||
|
$6, -- persona
|
||||||
|
$7 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: GetPracticeByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
FROM practices
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListPracticesByOwner :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
FROM practices
|
||||||
|
WHERE owner_type = $1
|
||||||
|
AND owner_id = $2
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY id ASC;
|
||||||
|
|
||||||
|
-- name: UpdatePractice :one
|
||||||
|
UPDATE practices
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
description = $3,
|
||||||
|
banner_image = $4,
|
||||||
|
persona = $5,
|
||||||
|
is_active = $6
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: DeactivatePractice :exec
|
||||||
|
UPDATE practices
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1;
|
||||||
88
db/query/program_levels.sql
Normal file
88
db/query/program_levels.sql
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
-- name: CreateLevel :one
|
||||||
|
INSERT INTO levels (
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- program_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4, -- level_index
|
||||||
|
$5, -- number_of_modules
|
||||||
|
$6, -- number_of_practices
|
||||||
|
$7, -- number_of_videos
|
||||||
|
$8 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: GetLevelByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
FROM levels
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListLevelsByProgram :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
FROM levels
|
||||||
|
WHERE program_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY level_index ASC;
|
||||||
|
|
||||||
|
-- name: UpdateLevel :one
|
||||||
|
UPDATE levels
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
description = $3,
|
||||||
|
level_index = $4,
|
||||||
|
number_of_modules = $5,
|
||||||
|
number_of_practices = $6,
|
||||||
|
number_of_videos = $7,
|
||||||
|
is_active = $8
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active;
|
||||||
|
|
||||||
|
-- name: DeactivateLevel :exec
|
||||||
|
UPDATE levels
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1;
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
-- -- name: CreateReferralCode :one
|
|
||||||
-- INSERT INTO referral_codes (
|
|
||||||
-- referral_code,
|
|
||||||
-- referrer_id,
|
|
||||||
-- company_id,
|
|
||||||
-- number_of_referrals,
|
|
||||||
-- reward_amount
|
|
||||||
-- )
|
|
||||||
-- VALUES ($1, $2, $3, $4, $5)
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: CreateUserReferral :one
|
|
||||||
-- INSERT INTO user_referrals (referred_id, referral_code_id)
|
|
||||||
-- VALUES ($1, $2)
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: GetReferralCodeByUser :many
|
|
||||||
-- SELECt *
|
|
||||||
-- FROM referral_codes
|
|
||||||
-- WHERE referrer_id = $1;
|
|
||||||
-- -- name: GetReferralCode :one
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM referral_codes
|
|
||||||
-- WHERE referral_code = $1;
|
|
||||||
-- -- name: UpdateReferralCode :exec
|
|
||||||
-- UPDATE referral_codes
|
|
||||||
-- SET is_active = $2,
|
|
||||||
-- referral_code = $3,
|
|
||||||
-- number_of_referrals = $4,
|
|
||||||
-- reward_amount = $5,
|
|
||||||
-- updated_at = CURRENT_TIMESTAMP
|
|
||||||
-- WHERE id = $1;
|
|
||||||
-- -- name: GetReferralStats :one
|
|
||||||
-- SELECT COUNT(*) AS total_referrals,
|
|
||||||
-- COALESCE(SUM(reward_amount), 0)::bigint AS total_reward_earned
|
|
||||||
-- FROM user_referrals
|
|
||||||
-- JOIN referral_codes ON referral_codes.id = referral_code_id
|
|
||||||
-- WHERE referrer_id = $1
|
|
||||||
-- AND company_id = $2;
|
|
||||||
-- -- name: GetUserReferral :one
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM user_referrals
|
|
||||||
-- WHERE referred_id = $1;
|
|
||||||
-- -- name: GetUserReferralsByCode :many
|
|
||||||
-- SELECT user_referrals.*
|
|
||||||
-- FROM user_referrals
|
|
||||||
-- JOIN referral_codes ON referral_codes.id = referral_code_id
|
|
||||||
-- WHERE referral_code = $1;
|
|
||||||
-- -- name: GetUserReferralsCount :one
|
|
||||||
-- SELECT COUNT(*)
|
|
||||||
-- FROM user_referrals
|
|
||||||
-- JOIN referral_codes ON referral_codes.id = referral_code_id
|
|
||||||
-- WHERE referrer_id = $1;
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
-- -- name: CreateShopTransaction :one
|
|
||||||
-- INSERT INTO shop_transactions (
|
|
||||||
-- amount,
|
|
||||||
-- branch_id,
|
|
||||||
-- company_id,
|
|
||||||
-- user_id,
|
|
||||||
-- type,
|
|
||||||
-- full_name,
|
|
||||||
-- phone_number,
|
|
||||||
-- payment_option,
|
|
||||||
-- bank_code,
|
|
||||||
-- beneficiary_name,
|
|
||||||
-- account_name,
|
|
||||||
-- account_number,
|
|
||||||
-- reference_number
|
|
||||||
-- )
|
|
||||||
-- VALUES (
|
|
||||||
-- $1,
|
|
||||||
-- $2,
|
|
||||||
-- $3,
|
|
||||||
-- $4,
|
|
||||||
-- $5,
|
|
||||||
-- $6,
|
|
||||||
-- $7,
|
|
||||||
-- $8,
|
|
||||||
-- $9,
|
|
||||||
-- $10,
|
|
||||||
-- $11,
|
|
||||||
-- $12,
|
|
||||||
-- $13
|
|
||||||
-- )
|
|
||||||
-- RETURNING *;
|
|
||||||
-- -- name: GetAllShopTransactions :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM shop_transaction_detail
|
|
||||||
-- wHERE (
|
|
||||||
-- branch_id = sqlc.narg('branch_id')
|
|
||||||
-- OR sqlc.narg('branch_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- company_id = sqlc.narg('company_id')
|
|
||||||
-- OR sqlc.narg('company_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- user_id = sqlc.narg('user_id')
|
|
||||||
-- OR sqlc.narg('user_id') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- full_name ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR phone_number ILIKE '%' || sqlc.narg('query') || '%'
|
|
||||||
-- OR sqlc.narg('query') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- created_at > sqlc.narg('created_before')
|
|
||||||
-- OR sqlc.narg('created_before') IS NULL
|
|
||||||
-- )
|
|
||||||
-- AND (
|
|
||||||
-- created_at < sqlc.narg('created_after')
|
|
||||||
-- OR sqlc.narg('created_after') IS NULL
|
|
||||||
-- );
|
|
||||||
-- -- name: GetShopTransactionByID :one
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM shop_transaction_detail
|
|
||||||
-- WHERE id = $1;
|
|
||||||
-- -- name: GetShopTransactionByBranch :many
|
|
||||||
-- SELECT *
|
|
||||||
-- FROM shop_transaction_detail
|
|
||||||
-- WHERE branch_id = $1;
|
|
||||||
-- -- name: UpdateShopTransactionVerified :exec
|
|
||||||
-- UPDATE shop_transactions
|
|
||||||
-- SET verified = $2,
|
|
||||||
-- approved_by = $3,
|
|
||||||
-- updated_at = CURRENT_TIMESTAMP
|
|
||||||
-- WHERE id = $1;
|
|
||||||
1197
docs/docs.go
1197
docs/docs.go
File diff suppressed because it is too large
Load Diff
1197
docs/swagger.json
1197
docs/swagger.json
File diff suppressed because it is too large
Load Diff
|
|
@ -27,6 +27,34 @@ definitions:
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.Course:
|
||||||
|
properties:
|
||||||
|
categoryID:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.CourseCategory:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
name:
|
||||||
|
description: '"Learning English", "Other Courses"'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.ErrorResponse:
|
domain.ErrorResponse:
|
||||||
properties:
|
properties:
|
||||||
error:
|
error:
|
||||||
|
|
@ -34,6 +62,31 @@ definitions:
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.Level:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
levelIndex:
|
||||||
|
description: 1,2,3...
|
||||||
|
type: integer
|
||||||
|
numberOfModules:
|
||||||
|
type: integer
|
||||||
|
numberOfPractices:
|
||||||
|
type: integer
|
||||||
|
numberOfVideos:
|
||||||
|
type: integer
|
||||||
|
programID:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
title:
|
||||||
|
description: '"Beginner", "Level 1"'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.LogEntry:
|
domain.LogEntry:
|
||||||
properties:
|
properties:
|
||||||
caller:
|
caller:
|
||||||
|
|
@ -65,6 +118,52 @@ definitions:
|
||||||
pagination:
|
pagination:
|
||||||
$ref: '#/definitions/domain.Pagination'
|
$ref: '#/definitions/domain.Pagination'
|
||||||
type: object
|
type: object
|
||||||
|
domain.Module:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
levelID:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ModuleVideo:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
duration:
|
||||||
|
description: seconds
|
||||||
|
type: integer
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
instructorId:
|
||||||
|
type: string
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
moduleID:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
publishSettings:
|
||||||
|
$ref: '#/definitions/domain.PublishSettings'
|
||||||
|
resolution:
|
||||||
|
description: '"720p", "1080p"'
|
||||||
|
type: string
|
||||||
|
thumbnail:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
videoURL:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.OtpMedium:
|
domain.OtpMedium:
|
||||||
enum:
|
enum:
|
||||||
- email
|
- email
|
||||||
|
|
@ -84,6 +183,80 @@ definitions:
|
||||||
total_pages:
|
total_pages:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
domain.Practice:
|
||||||
|
properties:
|
||||||
|
bannerImage:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
ownerID:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
ownerType:
|
||||||
|
description: '"LEVEL" | "MODULE"'
|
||||||
|
type: string
|
||||||
|
persona:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.PracticeQuestion:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
practiceID:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
question:
|
||||||
|
type: string
|
||||||
|
questionVoicePrompt:
|
||||||
|
type: string
|
||||||
|
sampleAnswer:
|
||||||
|
type: string
|
||||||
|
sampleAnswerVoicePrompt:
|
||||||
|
type: string
|
||||||
|
tips:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: MCQ, TRUE_FALSE, SHORT
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.Program:
|
||||||
|
properties:
|
||||||
|
courseID:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
order:
|
||||||
|
description: ordering inside course
|
||||||
|
type: integer
|
||||||
|
thumbnail:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.PublishSettings:
|
||||||
|
properties:
|
||||||
|
isPublished:
|
||||||
|
type: boolean
|
||||||
|
publishDate:
|
||||||
|
type: string
|
||||||
|
visibility:
|
||||||
|
description: '"public", "private", "unlisted"'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.RegisterUserReq:
|
domain.RegisterUserReq:
|
||||||
properties:
|
properties:
|
||||||
age:
|
age:
|
||||||
|
|
@ -1198,6 +1371,471 @@ paths:
|
||||||
summary: Refresh token
|
summary: Refresh token
|
||||||
tags:
|
tags:
|
||||||
- auth
|
- auth
|
||||||
|
/api/v1/course-categories:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Returns all active course categories
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.CourseCategory'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: List active course categories
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Creates a new course category
|
||||||
|
parameters:
|
||||||
|
- description: Course category payload
|
||||||
|
in: body
|
||||||
|
name: category
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.CourseCategory'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.CourseCategory'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Create course category
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/course-categories/{category_id}/courses:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Returns courses under a given category
|
||||||
|
parameters:
|
||||||
|
- description: Category ID
|
||||||
|
in: path
|
||||||
|
name: category_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.Course'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: List courses by category
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/course-categories/{id}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get course category by ID
|
||||||
|
parameters:
|
||||||
|
- description: Category ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.CourseCategory'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Get course category
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Updates a course category
|
||||||
|
parameters:
|
||||||
|
- description: Category ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Course category payload
|
||||||
|
in: body
|
||||||
|
name: category
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.CourseCategory'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.CourseCategory'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Update course category
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/course-categories/{id}/deactivate:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Deactivates a course category
|
||||||
|
parameters:
|
||||||
|
- description: Category ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Deactivate course category
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/courses:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Returns all active courses
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.Course'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: List active courses
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Creates a new course
|
||||||
|
parameters:
|
||||||
|
- description: Course payload
|
||||||
|
in: body
|
||||||
|
name: course
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Course'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.Course'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Create course
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/courses/{course_id}/programs:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: Course ID
|
||||||
|
in: path
|
||||||
|
name: course_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.Program'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
summary: List programs by course
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Program payload
|
||||||
|
in: body
|
||||||
|
name: program
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Program'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.Program'
|
||||||
|
type: object
|
||||||
|
summary: Create program
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/courses/{id}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get course by ID
|
||||||
|
parameters:
|
||||||
|
- description: Course ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.Course'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Get course
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Updates a course
|
||||||
|
parameters:
|
||||||
|
- description: Course ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Course payload
|
||||||
|
in: body
|
||||||
|
name: course
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Course'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.Course'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Update course
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/courses/{id}/deactivate:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Deactivates a course
|
||||||
|
parameters:
|
||||||
|
- description: Course ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Deactivate course
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/levels:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Level payload
|
||||||
|
in: body
|
||||||
|
name: level
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Level'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.Level'
|
||||||
|
type: object
|
||||||
|
summary: Create level
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/levels/{level_id}/modules:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: Level ID
|
||||||
|
in: path
|
||||||
|
name: level_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.Module'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
summary: List modules by level
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
/api/v1/logs:
|
/api/v1/logs:
|
||||||
get:
|
get:
|
||||||
description: Fetches application logs from MongoDB with pagination, level filtering,
|
description: Fetches application logs from MongoDB with pagination, level filtering,
|
||||||
|
|
@ -1240,6 +1878,110 @@ paths:
|
||||||
summary: Retrieve application logs with filtering and pagination
|
summary: Retrieve application logs with filtering and pagination
|
||||||
tags:
|
tags:
|
||||||
- Logs
|
- Logs
|
||||||
|
/api/v1/module-videos:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Module video payload
|
||||||
|
in: body
|
||||||
|
name: video
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ModuleVideo'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.ModuleVideo'
|
||||||
|
type: object
|
||||||
|
summary: Create module video
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/modules:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Module payload
|
||||||
|
in: body
|
||||||
|
name: module
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Module'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.Module'
|
||||||
|
type: object
|
||||||
|
summary: Create module
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/practice-questions:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Practice question payload
|
||||||
|
in: body
|
||||||
|
name: question
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.PracticeQuestion'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.PracticeQuestion'
|
||||||
|
type: object
|
||||||
|
summary: Create practice question
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
|
/api/v1/practices:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Practice payload
|
||||||
|
in: body
|
||||||
|
name: practice
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Practice'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/domain.Practice'
|
||||||
|
type: object
|
||||||
|
summary: Create practice
|
||||||
|
tags:
|
||||||
|
- courses
|
||||||
/api/v1/sendSMS:
|
/api/v1/sendSMS:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
||||||
143
gen/db/course_catagories.sql.go
Normal file
143
gen/db/course_catagories.sql.go
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: course_catagories.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateCourseCategory = `-- name: CreateCourseCategory :one
|
||||||
|
INSERT INTO course_categories (
|
||||||
|
name,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- name
|
||||||
|
$2 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCourseCategoryParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCourseCategory(ctx context.Context, arg CreateCourseCategoryParams) (CourseCategory, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateCourseCategory, arg.Name, arg.IsActive)
|
||||||
|
var i CourseCategory
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeactivateCourseCategory = `-- name: DeactivateCourseCategory :exec
|
||||||
|
UPDATE course_categories
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeactivateCourseCategory(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeactivateCourseCategory, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetCourseCategoryByID = `-- name: GetCourseCategoryByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at
|
||||||
|
FROM course_categories
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCourseCategoryByID(ctx context.Context, id int64) (CourseCategory, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetCourseCategoryByID, id)
|
||||||
|
var i CourseCategory
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListActiveCourseCategories = `-- name: ListActiveCourseCategories :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at
|
||||||
|
FROM course_categories
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListActiveCourseCategories(ctx context.Context) ([]CourseCategory, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListActiveCourseCategories)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CourseCategory
|
||||||
|
for rows.Next() {
|
||||||
|
var i CourseCategory
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateCourseCategory = `-- name: UpdateCourseCategory :one
|
||||||
|
UPDATE course_categories
|
||||||
|
SET
|
||||||
|
name = $2,
|
||||||
|
is_active = $3
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
is_active,
|
||||||
|
created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCourseCategoryParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCourseCategory(ctx context.Context, arg UpdateCourseCategoryParams) (CourseCategory, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateCourseCategory, arg.ID, arg.Name, arg.IsActive)
|
||||||
|
var i CourseCategory
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
247
gen/db/course_programs.sql.go
Normal file
247
gen/db/course_programs.sql.go
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: course_programs.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateProgram = `-- name: CreateProgram :one
|
||||||
|
INSERT INTO programs (
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- course_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4, -- thumbnail
|
||||||
|
$5, -- display_order
|
||||||
|
$6 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateProgramParams struct {
|
||||||
|
CourseID int64 `json:"course_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
DisplayOrder int32 `json:"display_order"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (Program, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateProgram,
|
||||||
|
arg.CourseID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.Thumbnail,
|
||||||
|
arg.DisplayOrder,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Program
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CourseID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeactivateProgram = `-- name: DeactivateProgram :exec
|
||||||
|
UPDATE programs
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeactivateProgram(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeactivateProgram, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetProgramByID = `-- name: GetProgramByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM programs
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetProgramByID(ctx context.Context, id int64) (Program, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetProgramByID, id)
|
||||||
|
var i Program
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CourseID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListActivePrograms = `-- name: ListActivePrograms :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM programs
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
ORDER BY display_order ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListActivePrograms(ctx context.Context) ([]Program, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListActivePrograms)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Program
|
||||||
|
for rows.Next() {
|
||||||
|
var i Program
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CourseID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListProgramsByCourse = `-- name: ListProgramsByCourse :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM programs
|
||||||
|
WHERE course_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY display_order ASC, id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListProgramsByCourse(ctx context.Context, courseID int64) ([]Program, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListProgramsByCourse, courseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Program
|
||||||
|
for rows.Next() {
|
||||||
|
var i Program
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CourseID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateProgram = `-- name: UpdateProgram :one
|
||||||
|
UPDATE programs
|
||||||
|
SET
|
||||||
|
course_id = $2,
|
||||||
|
title = $3,
|
||||||
|
description = $4,
|
||||||
|
thumbnail = $5,
|
||||||
|
display_order = $6,
|
||||||
|
is_active = $7
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
course_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateProgramParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CourseID int64 `json:"course_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
DisplayOrder int32 `json:"display_order"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateProgram(ctx context.Context, arg UpdateProgramParams) (Program, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateProgram,
|
||||||
|
arg.ID,
|
||||||
|
arg.CourseID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.Thumbnail,
|
||||||
|
arg.DisplayOrder,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Program
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CourseID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
213
gen/db/courses.sql.go
Normal file
213
gen/db/courses.sql.go
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: courses.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateCourse = `-- name: CreateCourse :one
|
||||||
|
INSERT INTO courses (
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- category_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCourseParams struct {
|
||||||
|
CategoryID int64 `json:"category_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Course, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateCourse,
|
||||||
|
arg.CategoryID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Course
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CategoryID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeactivateCourse = `-- name: DeactivateCourse :exec
|
||||||
|
UPDATE courses
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeactivateCourse(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeactivateCourse, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetCourseByID = `-- name: GetCourseByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
FROM courses
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCourseByID(ctx context.Context, id int64) (Course, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetCourseByID, id)
|
||||||
|
var i Course
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CategoryID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListActiveCourses = `-- name: ListActiveCourses :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
FROM courses
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
ORDER BY id DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListActiveCourses(ctx context.Context) ([]Course, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListActiveCourses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Course
|
||||||
|
for rows.Next() {
|
||||||
|
var i Course
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CategoryID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListCoursesByCategory = `-- name: ListCoursesByCategory :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
FROM courses
|
||||||
|
WHERE category_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY id DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListCoursesByCategory(ctx context.Context, categoryID int64) ([]Course, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListCoursesByCategory, categoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Course
|
||||||
|
for rows.Next() {
|
||||||
|
var i Course
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CategoryID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateCourse = `-- name: UpdateCourse :one
|
||||||
|
UPDATE courses
|
||||||
|
SET
|
||||||
|
category_id = $2,
|
||||||
|
title = $3,
|
||||||
|
description = $4,
|
||||||
|
is_active = $5
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
category_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCourseParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CategoryID int64 `json:"category_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCourse(ctx context.Context, arg UpdateCourseParams) (Course, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateCourse,
|
||||||
|
arg.ID,
|
||||||
|
arg.CategoryID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Course
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CategoryID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
187
gen/db/level_modules.sql.go
Normal file
187
gen/db/level_modules.sql.go
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: level_modules.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateModule = `-- name: CreateModule :one
|
||||||
|
INSERT INTO modules (
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- level_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- content
|
||||||
|
$4, -- display_order
|
||||||
|
$5 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateModuleParams struct {
|
||||||
|
LevelID int64 `json:"level_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content pgtype.Text `json:"content"`
|
||||||
|
DisplayOrder int32 `json:"display_order"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateModule,
|
||||||
|
arg.LevelID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Content,
|
||||||
|
arg.DisplayOrder,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Module
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.LevelID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Content,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeactivateModule = `-- name: DeactivateModule :exec
|
||||||
|
UPDATE modules
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeactivateModule(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeactivateModule, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetModuleByID = `-- name: GetModuleByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM modules
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetModuleByID(ctx context.Context, id int64) (Module, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetModuleByID, id)
|
||||||
|
var i Module
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.LevelID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Content,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListModulesByLevel = `-- name: ListModulesByLevel :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
FROM modules
|
||||||
|
WHERE level_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY display_order ASC, id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListModulesByLevel(ctx context.Context, levelID int64) ([]Module, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListModulesByLevel, levelID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Module
|
||||||
|
for rows.Next() {
|
||||||
|
var i Module
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.LevelID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Content,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateModule = `-- name: UpdateModule :one
|
||||||
|
UPDATE modules
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
content = $3,
|
||||||
|
display_order = $4,
|
||||||
|
is_active = $5
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
level_id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
display_order,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateModuleParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content pgtype.Text `json:"content"`
|
||||||
|
DisplayOrder int32 `json:"display_order"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateModule(ctx context.Context, arg UpdateModuleParams) (Module, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateModule,
|
||||||
|
arg.ID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Content,
|
||||||
|
arg.DisplayOrder,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Module
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.LevelID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Content,
|
||||||
|
&i.DisplayOrder,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
129
gen/db/models.go
129
gen/db/models.go
|
|
@ -8,16 +8,6 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Assessment struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
CourseID int64 `json:"course_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
TotalScore int32 `json:"total_score"`
|
|
||||||
DueDate pgtype.Timestamptz `json:"due_date"`
|
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AssessmentAnswer struct {
|
type AssessmentAnswer struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
AttemptID int64 `json:"attempt_id"`
|
AttemptID int64 `json:"attempt_id"`
|
||||||
|
|
@ -55,44 +45,21 @@ type AssessmentQuestionOption struct {
|
||||||
IsCorrect bool `json:"is_correct"`
|
IsCorrect bool `json:"is_correct"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssessmentSubmission struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
AssessmentID int64 `json:"assessment_id"`
|
|
||||||
StudentID int64 `json:"student_id"`
|
|
||||||
Score pgtype.Int4 `json:"score"`
|
|
||||||
Feedback pgtype.Text `json:"feedback"`
|
|
||||||
SubmittedAt pgtype.Timestamptz `json:"submitted_at"`
|
|
||||||
GradedAt pgtype.Timestamptz `json:"graded_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Course struct {
|
type Course struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
InstructorID int64 `json:"instructor_id"`
|
CategoryID int64 `json:"category_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description pgtype.Text `json:"description"`
|
Description pgtype.Text `json:"description"`
|
||||||
Level pgtype.Text `json:"level"`
|
IsActive bool `json:"is_active"`
|
||||||
Language pgtype.Text `json:"language"`
|
|
||||||
IsPublished bool `json:"is_published"`
|
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CourseModule struct {
|
type CourseCategory struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
CourseID int64 `json:"course_id"`
|
Name string `json:"name"`
|
||||||
Title string `json:"title"`
|
IsActive bool `json:"is_active"`
|
||||||
Position int32 `json:"position"`
|
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Enrollment struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
CourseID int64 `json:"course_id"`
|
|
||||||
StudentID int64 `json:"student_id"`
|
|
||||||
EnrolledAt pgtype.Timestamptz `json:"enrolled_at"`
|
|
||||||
CompletedAt pgtype.Timestamptz `json:"completed_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GlobalSetting struct {
|
type GlobalSetting struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
|
|
@ -100,23 +67,41 @@ type GlobalSetting struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Lesson struct {
|
type Level struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
ModuleID int64 `json:"module_id"`
|
ProgramID int64 `json:"program_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
ContentType string `json:"content_type"`
|
Description pgtype.Text `json:"description"`
|
||||||
ContentUrl pgtype.Text `json:"content_url"`
|
LevelIndex int32 `json:"level_index"`
|
||||||
DurationMinutes pgtype.Int4 `json:"duration_minutes"`
|
NumberOfModules int32 `json:"number_of_modules"`
|
||||||
Position int32 `json:"position"`
|
NumberOfPractices int32 `json:"number_of_practices"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
NumberOfVideos int32 `json:"number_of_videos"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LessonProgress struct {
|
type Module struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
LessonID int64 `json:"lesson_id"`
|
LevelID int64 `json:"level_id"`
|
||||||
StudentID int64 `json:"student_id"`
|
Title string `json:"title"`
|
||||||
Completed bool `json:"completed"`
|
Content pgtype.Text `json:"content"`
|
||||||
CompletedAt pgtype.Timestamptz `json:"completed_at"`
|
DisplayOrder int32 `json:"display_order"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleVideo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ModuleID int64 `json:"module_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
VideoUrl string `json:"video_url"`
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
Resolution pgtype.Text `json:"resolution"`
|
||||||
|
IsPublished bool `json:"is_published"`
|
||||||
|
PublishDate pgtype.Timestamptz `json:"publish_date"`
|
||||||
|
Visibility pgtype.Text `json:"visibility"`
|
||||||
|
InstructorID pgtype.Text `json:"instructor_id"`
|
||||||
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
|
|
@ -146,6 +131,38 @@ type Otp struct {
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Practice struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
OwnerType string `json:"owner_type"`
|
||||||
|
OwnerID int64 `json:"owner_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
BannerImage pgtype.Text `json:"banner_image"`
|
||||||
|
Persona pgtype.Text `json:"persona"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PracticeQuestion struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
PracticeID int64 `json:"practice_id"`
|
||||||
|
Question string `json:"question"`
|
||||||
|
QuestionVoicePrompt pgtype.Text `json:"question_voice_prompt"`
|
||||||
|
SampleAnswerVoicePrompt pgtype.Text `json:"sample_answer_voice_prompt"`
|
||||||
|
SampleAnswer pgtype.Text `json:"sample_answer"`
|
||||||
|
Tips pgtype.Text `json:"tips"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CourseID int64 `json:"course_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
DisplayOrder int32 `json:"display_order"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
type RefreshToken struct {
|
type RefreshToken struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
|
|
||||||
363
gen/db/module_videos.sql.go
Normal file
363
gen/db/module_videos.sql.go
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: module_videos.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateModuleVideo = `-- name: CreateModuleVideo :one
|
||||||
|
INSERT INTO module_videos (
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- module_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4, -- video_url
|
||||||
|
$5, -- duration
|
||||||
|
$6, -- resolution
|
||||||
|
|
||||||
|
$7, -- is_published
|
||||||
|
$8, -- publish_date
|
||||||
|
$9, -- visibility
|
||||||
|
|
||||||
|
$10, -- instructor_id
|
||||||
|
$11, -- thumbnail
|
||||||
|
$12 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateModuleVideoParams struct {
|
||||||
|
ModuleID int64 `json:"module_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
VideoUrl string `json:"video_url"`
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
Resolution pgtype.Text `json:"resolution"`
|
||||||
|
IsPublished bool `json:"is_published"`
|
||||||
|
PublishDate pgtype.Timestamptz `json:"publish_date"`
|
||||||
|
Visibility pgtype.Text `json:"visibility"`
|
||||||
|
InstructorID pgtype.Text `json:"instructor_id"`
|
||||||
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateModuleVideo(ctx context.Context, arg CreateModuleVideoParams) (ModuleVideo, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateModuleVideo,
|
||||||
|
arg.ModuleID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.VideoUrl,
|
||||||
|
arg.Duration,
|
||||||
|
arg.Resolution,
|
||||||
|
arg.IsPublished,
|
||||||
|
arg.PublishDate,
|
||||||
|
arg.Visibility,
|
||||||
|
arg.InstructorID,
|
||||||
|
arg.Thumbnail,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i ModuleVideo
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ModuleID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.VideoUrl,
|
||||||
|
&i.Duration,
|
||||||
|
&i.Resolution,
|
||||||
|
&i.IsPublished,
|
||||||
|
&i.PublishDate,
|
||||||
|
&i.Visibility,
|
||||||
|
&i.InstructorID,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeactivateModuleVideo = `-- name: DeactivateModuleVideo :exec
|
||||||
|
UPDATE module_videos
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeactivateModuleVideo(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeactivateModuleVideo, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetModuleVideoByID = `-- name: GetModuleVideoByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
FROM module_videos
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetModuleVideoByID(ctx context.Context, id int64) (ModuleVideo, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetModuleVideoByID, id)
|
||||||
|
var i ModuleVideo
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ModuleID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.VideoUrl,
|
||||||
|
&i.Duration,
|
||||||
|
&i.Resolution,
|
||||||
|
&i.IsPublished,
|
||||||
|
&i.PublishDate,
|
||||||
|
&i.Visibility,
|
||||||
|
&i.InstructorID,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListAllVideosByModule = `-- name: ListAllVideosByModule :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
FROM module_videos
|
||||||
|
WHERE module_id = $1
|
||||||
|
ORDER BY id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListAllVideosByModule(ctx context.Context, moduleID int64) ([]ModuleVideo, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListAllVideosByModule, moduleID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ModuleVideo
|
||||||
|
for rows.Next() {
|
||||||
|
var i ModuleVideo
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ModuleID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.VideoUrl,
|
||||||
|
&i.Duration,
|
||||||
|
&i.Resolution,
|
||||||
|
&i.IsPublished,
|
||||||
|
&i.PublishDate,
|
||||||
|
&i.Visibility,
|
||||||
|
&i.InstructorID,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListPublishedVideosByModule = `-- name: ListPublishedVideosByModule :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail
|
||||||
|
FROM module_videos
|
||||||
|
WHERE module_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
AND is_published = TRUE
|
||||||
|
ORDER BY publish_date ASC, id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListPublishedVideosByModuleRow struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ModuleID int64 `json:"module_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
VideoUrl string `json:"video_url"`
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
Resolution pgtype.Text `json:"resolution"`
|
||||||
|
PublishDate pgtype.Timestamptz `json:"publish_date"`
|
||||||
|
Visibility pgtype.Text `json:"visibility"`
|
||||||
|
InstructorID pgtype.Text `json:"instructor_id"`
|
||||||
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListPublishedVideosByModule(ctx context.Context, moduleID int64) ([]ListPublishedVideosByModuleRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListPublishedVideosByModule, moduleID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ListPublishedVideosByModuleRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i ListPublishedVideosByModuleRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ModuleID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.VideoUrl,
|
||||||
|
&i.Duration,
|
||||||
|
&i.Resolution,
|
||||||
|
&i.PublishDate,
|
||||||
|
&i.Visibility,
|
||||||
|
&i.InstructorID,
|
||||||
|
&i.Thumbnail,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateModuleVideo = `-- name: UpdateModuleVideo :one
|
||||||
|
UPDATE module_videos
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
description = $3,
|
||||||
|
video_url = $4,
|
||||||
|
duration = $5,
|
||||||
|
resolution = $6,
|
||||||
|
|
||||||
|
is_published = $7,
|
||||||
|
publish_date = $8,
|
||||||
|
visibility = $9,
|
||||||
|
|
||||||
|
instructor_id = $10,
|
||||||
|
thumbnail = $11,
|
||||||
|
is_active = $12
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
module_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
video_url,
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
is_published,
|
||||||
|
publish_date,
|
||||||
|
visibility,
|
||||||
|
instructor_id,
|
||||||
|
thumbnail,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateModuleVideoParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
VideoUrl string `json:"video_url"`
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
Resolution pgtype.Text `json:"resolution"`
|
||||||
|
IsPublished bool `json:"is_published"`
|
||||||
|
PublishDate pgtype.Timestamptz `json:"publish_date"`
|
||||||
|
Visibility pgtype.Text `json:"visibility"`
|
||||||
|
InstructorID pgtype.Text `json:"instructor_id"`
|
||||||
|
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateModuleVideo(ctx context.Context, arg UpdateModuleVideoParams) (ModuleVideo, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateModuleVideo,
|
||||||
|
arg.ID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.VideoUrl,
|
||||||
|
arg.Duration,
|
||||||
|
arg.Resolution,
|
||||||
|
arg.IsPublished,
|
||||||
|
arg.PublishDate,
|
||||||
|
arg.Visibility,
|
||||||
|
arg.InstructorID,
|
||||||
|
arg.Thumbnail,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i ModuleVideo
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ModuleID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.VideoUrl,
|
||||||
|
&i.Duration,
|
||||||
|
&i.Resolution,
|
||||||
|
&i.IsPublished,
|
||||||
|
&i.PublishDate,
|
||||||
|
&i.Visibility,
|
||||||
|
&i.InstructorID,
|
||||||
|
&i.Thumbnail,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
215
gen/db/practice_questions.sql.go
Normal file
215
gen/db/practice_questions.sql.go
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: practice_questions.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreatePracticeQuestion = `-- name: CreatePracticeQuestion :one
|
||||||
|
INSERT INTO practice_questions (
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- practice_id
|
||||||
|
$2, -- question
|
||||||
|
$3, -- question_voice_prompt
|
||||||
|
$4, -- sample_answer_voice_prompt
|
||||||
|
$5, -- sample_answer
|
||||||
|
$6, -- tips
|
||||||
|
$7 -- type (MCQ, TRUE_FALSE, SHORT)
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreatePracticeQuestionParams struct {
|
||||||
|
PracticeID int64 `json:"practice_id"`
|
||||||
|
Question string `json:"question"`
|
||||||
|
QuestionVoicePrompt pgtype.Text `json:"question_voice_prompt"`
|
||||||
|
SampleAnswerVoicePrompt pgtype.Text `json:"sample_answer_voice_prompt"`
|
||||||
|
SampleAnswer pgtype.Text `json:"sample_answer"`
|
||||||
|
Tips pgtype.Text `json:"tips"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreatePracticeQuestion(ctx context.Context, arg CreatePracticeQuestionParams) (PracticeQuestion, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreatePracticeQuestion,
|
||||||
|
arg.PracticeID,
|
||||||
|
arg.Question,
|
||||||
|
arg.QuestionVoicePrompt,
|
||||||
|
arg.SampleAnswerVoicePrompt,
|
||||||
|
arg.SampleAnswer,
|
||||||
|
arg.Tips,
|
||||||
|
arg.Type,
|
||||||
|
)
|
||||||
|
var i PracticeQuestion
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.PracticeID,
|
||||||
|
&i.Question,
|
||||||
|
&i.QuestionVoicePrompt,
|
||||||
|
&i.SampleAnswerVoicePrompt,
|
||||||
|
&i.SampleAnswer,
|
||||||
|
&i.Tips,
|
||||||
|
&i.Type,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeletePracticeQuestion = `-- name: DeletePracticeQuestion :exec
|
||||||
|
DELETE FROM practice_questions
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeletePracticeQuestion(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeletePracticeQuestion, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetPracticeQuestionByID = `-- name: GetPracticeQuestionByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
FROM practice_questions
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetPracticeQuestionByID(ctx context.Context, id int64) (PracticeQuestion, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetPracticeQuestionByID, id)
|
||||||
|
var i PracticeQuestion
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.PracticeID,
|
||||||
|
&i.Question,
|
||||||
|
&i.QuestionVoicePrompt,
|
||||||
|
&i.SampleAnswerVoicePrompt,
|
||||||
|
&i.SampleAnswer,
|
||||||
|
&i.Tips,
|
||||||
|
&i.Type,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListPracticeQuestions = `-- name: ListPracticeQuestions :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
FROM practice_questions
|
||||||
|
WHERE practice_id = $1
|
||||||
|
ORDER BY id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListPracticeQuestions(ctx context.Context, practiceID int64) ([]PracticeQuestion, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListPracticeQuestions, practiceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []PracticeQuestion
|
||||||
|
for rows.Next() {
|
||||||
|
var i PracticeQuestion
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.PracticeID,
|
||||||
|
&i.Question,
|
||||||
|
&i.QuestionVoicePrompt,
|
||||||
|
&i.SampleAnswerVoicePrompt,
|
||||||
|
&i.SampleAnswer,
|
||||||
|
&i.Tips,
|
||||||
|
&i.Type,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdatePracticeQuestion = `-- name: UpdatePracticeQuestion :one
|
||||||
|
UPDATE practice_questions
|
||||||
|
SET
|
||||||
|
question = $2,
|
||||||
|
question_voice_prompt = $3,
|
||||||
|
sample_answer_voice_prompt = $4,
|
||||||
|
sample_answer = $5,
|
||||||
|
tips = $6,
|
||||||
|
type = $7
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
practice_id,
|
||||||
|
question,
|
||||||
|
question_voice_prompt,
|
||||||
|
sample_answer_voice_prompt,
|
||||||
|
sample_answer,
|
||||||
|
tips,
|
||||||
|
type
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdatePracticeQuestionParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Question string `json:"question"`
|
||||||
|
QuestionVoicePrompt pgtype.Text `json:"question_voice_prompt"`
|
||||||
|
SampleAnswerVoicePrompt pgtype.Text `json:"sample_answer_voice_prompt"`
|
||||||
|
SampleAnswer pgtype.Text `json:"sample_answer"`
|
||||||
|
Tips pgtype.Text `json:"tips"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdatePracticeQuestion(ctx context.Context, arg UpdatePracticeQuestionParams) (PracticeQuestion, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdatePracticeQuestion,
|
||||||
|
arg.ID,
|
||||||
|
arg.Question,
|
||||||
|
arg.QuestionVoicePrompt,
|
||||||
|
arg.SampleAnswerVoicePrompt,
|
||||||
|
arg.SampleAnswer,
|
||||||
|
arg.Tips,
|
||||||
|
arg.Type,
|
||||||
|
)
|
||||||
|
var i PracticeQuestion
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.PracticeID,
|
||||||
|
&i.Question,
|
||||||
|
&i.QuestionVoicePrompt,
|
||||||
|
&i.SampleAnswerVoicePrompt,
|
||||||
|
&i.SampleAnswer,
|
||||||
|
&i.Tips,
|
||||||
|
&i.Type,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
220
gen/db/practices.sql.go
Normal file
220
gen/db/practices.sql.go
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: practices.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreatePractice = `-- name: CreatePractice :one
|
||||||
|
INSERT INTO practices (
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- owner_type (LEVEL | MODULE)
|
||||||
|
$2, -- owner_id
|
||||||
|
$3, -- title
|
||||||
|
$4, -- description
|
||||||
|
$5, -- banner_image
|
||||||
|
$6, -- persona
|
||||||
|
$7 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreatePracticeParams struct {
|
||||||
|
OwnerType string `json:"owner_type"`
|
||||||
|
OwnerID int64 `json:"owner_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
BannerImage pgtype.Text `json:"banner_image"`
|
||||||
|
Persona pgtype.Text `json:"persona"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreatePractice(ctx context.Context, arg CreatePracticeParams) (Practice, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreatePractice,
|
||||||
|
arg.OwnerType,
|
||||||
|
arg.OwnerID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.BannerImage,
|
||||||
|
arg.Persona,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Practice
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.OwnerType,
|
||||||
|
&i.OwnerID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.BannerImage,
|
||||||
|
&i.Persona,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeactivatePractice = `-- name: DeactivatePractice :exec
|
||||||
|
UPDATE practices
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeactivatePractice(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeactivatePractice, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetPracticeByID = `-- name: GetPracticeByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
FROM practices
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetPracticeByID(ctx context.Context, id int64) (Practice, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetPracticeByID, id)
|
||||||
|
var i Practice
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.OwnerType,
|
||||||
|
&i.OwnerID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.BannerImage,
|
||||||
|
&i.Persona,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListPracticesByOwner = `-- name: ListPracticesByOwner :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
FROM practices
|
||||||
|
WHERE owner_type = $1
|
||||||
|
AND owner_id = $2
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListPracticesByOwnerParams struct {
|
||||||
|
OwnerType string `json:"owner_type"`
|
||||||
|
OwnerID int64 `json:"owner_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListPracticesByOwner(ctx context.Context, arg ListPracticesByOwnerParams) ([]Practice, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListPracticesByOwner, arg.OwnerType, arg.OwnerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Practice
|
||||||
|
for rows.Next() {
|
||||||
|
var i Practice
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.OwnerType,
|
||||||
|
&i.OwnerID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.BannerImage,
|
||||||
|
&i.Persona,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdatePractice = `-- name: UpdatePractice :one
|
||||||
|
UPDATE practices
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
description = $3,
|
||||||
|
banner_image = $4,
|
||||||
|
persona = $5,
|
||||||
|
is_active = $6
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_type,
|
||||||
|
owner_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
banner_image,
|
||||||
|
persona,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdatePracticeParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
BannerImage pgtype.Text `json:"banner_image"`
|
||||||
|
Persona pgtype.Text `json:"persona"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdatePractice(ctx context.Context, arg UpdatePracticeParams) (Practice, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdatePractice,
|
||||||
|
arg.ID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.BannerImage,
|
||||||
|
arg.Persona,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Practice
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.OwnerType,
|
||||||
|
&i.OwnerID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.BannerImage,
|
||||||
|
&i.Persona,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
232
gen/db/program_levels.sql.go
Normal file
232
gen/db/program_levels.sql.go
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: program_levels.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateLevel = `-- name: CreateLevel :one
|
||||||
|
INSERT INTO levels (
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, -- program_id
|
||||||
|
$2, -- title
|
||||||
|
$3, -- description
|
||||||
|
$4, -- level_index
|
||||||
|
$5, -- number_of_modules
|
||||||
|
$6, -- number_of_practices
|
||||||
|
$7, -- number_of_videos
|
||||||
|
$8 -- is_active
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateLevelParams struct {
|
||||||
|
ProgramID int64 `json:"program_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
LevelIndex int32 `json:"level_index"`
|
||||||
|
NumberOfModules int32 `json:"number_of_modules"`
|
||||||
|
NumberOfPractices int32 `json:"number_of_practices"`
|
||||||
|
NumberOfVideos int32 `json:"number_of_videos"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateLevel(ctx context.Context, arg CreateLevelParams) (Level, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateLevel,
|
||||||
|
arg.ProgramID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.LevelIndex,
|
||||||
|
arg.NumberOfModules,
|
||||||
|
arg.NumberOfPractices,
|
||||||
|
arg.NumberOfVideos,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Level
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProgramID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.LevelIndex,
|
||||||
|
&i.NumberOfModules,
|
||||||
|
&i.NumberOfPractices,
|
||||||
|
&i.NumberOfVideos,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeactivateLevel = `-- name: DeactivateLevel :exec
|
||||||
|
UPDATE levels
|
||||||
|
SET is_active = FALSE
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeactivateLevel(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeactivateLevel, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetLevelByID = `-- name: GetLevelByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
FROM levels
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetLevelByID(ctx context.Context, id int64) (Level, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetLevelByID, id)
|
||||||
|
var i Level
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProgramID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.LevelIndex,
|
||||||
|
&i.NumberOfModules,
|
||||||
|
&i.NumberOfPractices,
|
||||||
|
&i.NumberOfVideos,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListLevelsByProgram = `-- name: ListLevelsByProgram :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
FROM levels
|
||||||
|
WHERE program_id = $1
|
||||||
|
AND is_active = TRUE
|
||||||
|
ORDER BY level_index ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListLevelsByProgram(ctx context.Context, programID int64) ([]Level, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListLevelsByProgram, programID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Level
|
||||||
|
for rows.Next() {
|
||||||
|
var i Level
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProgramID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.LevelIndex,
|
||||||
|
&i.NumberOfModules,
|
||||||
|
&i.NumberOfPractices,
|
||||||
|
&i.NumberOfVideos,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateLevel = `-- name: UpdateLevel :one
|
||||||
|
UPDATE levels
|
||||||
|
SET
|
||||||
|
title = $2,
|
||||||
|
description = $3,
|
||||||
|
level_index = $4,
|
||||||
|
number_of_modules = $5,
|
||||||
|
number_of_practices = $6,
|
||||||
|
number_of_videos = $7,
|
||||||
|
is_active = $8
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
program_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
level_index,
|
||||||
|
number_of_modules,
|
||||||
|
number_of_practices,
|
||||||
|
number_of_videos,
|
||||||
|
is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateLevelParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
LevelIndex int32 `json:"level_index"`
|
||||||
|
NumberOfModules int32 `json:"number_of_modules"`
|
||||||
|
NumberOfPractices int32 `json:"number_of_practices"`
|
||||||
|
NumberOfVideos int32 `json:"number_of_videos"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateLevel(ctx context.Context, arg UpdateLevelParams) (Level, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateLevel,
|
||||||
|
arg.ID,
|
||||||
|
arg.Title,
|
||||||
|
arg.Description,
|
||||||
|
arg.LevelIndex,
|
||||||
|
arg.NumberOfModules,
|
||||||
|
arg.NumberOfPractices,
|
||||||
|
arg.NumberOfVideos,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
var i Level
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProgramID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Description,
|
||||||
|
&i.LevelIndex,
|
||||||
|
&i.NumberOfModules,
|
||||||
|
&i.NumberOfPractices,
|
||||||
|
&i.NumberOfVideos,
|
||||||
|
&i.IsActive,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
91
internal/domain/courses.go
Normal file
91
internal/domain/courses.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type CourseCategory struct {
|
||||||
|
ID int64
|
||||||
|
Name string // "Learning English", "Other Courses"
|
||||||
|
IsActive bool
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type Course struct {
|
||||||
|
ID int64
|
||||||
|
CategoryID int64
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
IsActive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
ID int64
|
||||||
|
CourseID int64
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Thumbnail string
|
||||||
|
Order int // ordering inside course
|
||||||
|
IsActive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level struct {
|
||||||
|
ID int64
|
||||||
|
ProgramID int64
|
||||||
|
Title string // "Beginner", "Level 1"
|
||||||
|
Description string
|
||||||
|
LevelIndex int // 1,2,3...
|
||||||
|
NumberOfModules int
|
||||||
|
NumberOfPractices int
|
||||||
|
NumberOfVideos int
|
||||||
|
IsActive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Module struct {
|
||||||
|
ID int64
|
||||||
|
LevelID int64
|
||||||
|
Title string
|
||||||
|
Content string
|
||||||
|
Order int
|
||||||
|
IsActive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleVideo struct {
|
||||||
|
ID int64
|
||||||
|
ModuleID int64
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
VideoURL string
|
||||||
|
Duration int // seconds
|
||||||
|
Resolution string // "720p", "1080p"
|
||||||
|
PublishSettings PublishSettings
|
||||||
|
IsActive bool
|
||||||
|
InstructorId string
|
||||||
|
Thumbnail string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PublishSettings struct {
|
||||||
|
IsPublished bool
|
||||||
|
PublishDate time.Time
|
||||||
|
Visibility string // "public", "private", "unlisted"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Practice struct {
|
||||||
|
ID int64
|
||||||
|
OwnerType string // "LEVEL" | "MODULE"
|
||||||
|
OwnerID int64
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
BannerImage string
|
||||||
|
Persona string
|
||||||
|
IsActive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type PracticeQuestion struct {
|
||||||
|
ID int64
|
||||||
|
PracticeID int64
|
||||||
|
Question string
|
||||||
|
QuestionVoicePrompt string
|
||||||
|
SampleAnswerVoicePrompt string
|
||||||
|
SampleAnswer string
|
||||||
|
Tips string
|
||||||
|
Type string // MCQ, TRUE_FALSE, SHORT
|
||||||
|
}
|
||||||
60
internal/ports/course_management.go
Normal file
60
internal/ports/course_management.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package ports
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"Yimaru-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CourseStore interface{
|
||||||
|
CreateCourseCategory(ctx context.Context, name string) (domain.CourseCategory, error)
|
||||||
|
GetCourseCategoryByID(ctx context.Context, Id int64) (domain.CourseCategory, error)
|
||||||
|
ListActiveCourseCategories(ctx context.Context) ([]domain.CourseCategory, error)
|
||||||
|
UpdateCourseCategory(ctx context.Context, id int64, name string, isActive bool) (domain.CourseCategory, error)
|
||||||
|
DeactivateCourseCategory(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
CreateCourse(ctx context.Context, c domain.Course) (domain.Course, error)
|
||||||
|
GetCourseByID(ctx context.Context, id int64) (domain.Course, error)
|
||||||
|
ListCoursesByCategory(ctx context.Context, categoryID int64) ([]domain.Course, error)
|
||||||
|
ListActiveCourses(ctx context.Context) ([]domain.Course, error)
|
||||||
|
UpdateCourse(ctx context.Context, c domain.Course) (domain.Course, error)
|
||||||
|
DeactivateCourse(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
CreateProgram(ctx context.Context, p domain.Program) (domain.Program, error)
|
||||||
|
GetProgramByID(ctx context.Context, id int64) (domain.Program, error)
|
||||||
|
ListProgramsByCourse(ctx context.Context, courseID int64) ([]domain.Program, error)
|
||||||
|
ListActivePrograms(ctx context.Context) ([]domain.Program, error)
|
||||||
|
UpdateProgram(ctx context.Context, p domain.Program) (domain.Program, error)
|
||||||
|
DeactivateProgram(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
CreateModule(ctx context.Context, m domain.Module) (domain.Module, error)
|
||||||
|
GetModuleByID(ctx context.Context, id int64) (domain.Module, error)
|
||||||
|
ListModulesByLevel(ctx context.Context, levelID int64) ([]domain.Module, error)
|
||||||
|
UpdateModule(ctx context.Context, m domain.Module) (domain.Module, error)
|
||||||
|
DeactivateModule(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
CreateModuleVideo(ctx context.Context, v domain.ModuleVideo) (domain.ModuleVideo, error)
|
||||||
|
GetModuleVideoByID(ctx context.Context, id int64) (domain.ModuleVideo, error)
|
||||||
|
ListAllVideosByModule(ctx context.Context, moduleID int64) ([]domain.ModuleVideo, error)
|
||||||
|
ListPublishedVideosByModule(ctx context.Context, moduleID int64) ([]domain.ModuleVideo, error)
|
||||||
|
UpdateModuleVideo(ctx context.Context, v domain.ModuleVideo) (domain.ModuleVideo, error)
|
||||||
|
DeactivateModuleVideo(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
CreatePractice(ctx context.Context, p domain.Practice) (domain.Practice, error)
|
||||||
|
GetPracticeByID(ctx context.Context, id int64) (domain.Practice, error)
|
||||||
|
ListPracticesByOwner(ctx context.Context, ownerType string, ownerID int64) ([]domain.Practice, error)
|
||||||
|
UpdatePractice(ctx context.Context, p domain.Practice) (domain.Practice, error)
|
||||||
|
DeactivatePractice(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
CreatePracticeQuestion(ctx context.Context, qn domain.PracticeQuestion) (domain.PracticeQuestion, error)
|
||||||
|
GetPracticeQuestionByID(ctx context.Context, id int64) (domain.PracticeQuestion, error)
|
||||||
|
ListPracticeQuestions(ctx context.Context, practiceID int64) ([]domain.PracticeQuestion, error)
|
||||||
|
UpdatePracticeQuestion(ctx context.Context, qn domain.PracticeQuestion) (domain.PracticeQuestion, error)
|
||||||
|
DeletePracticeQuestion(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
CreateLevel(ctx context.Context, l domain.Level) (domain.Level, error)
|
||||||
|
GetLevelByID(ctx context.Context, id int64) (domain.Level, error)
|
||||||
|
ListLevelsByProgram(ctx context.Context, programID int64) ([]domain.Level, error)
|
||||||
|
UpdateLevel(ctx context.Context, l domain.Level) (domain.Level, error)
|
||||||
|
DeactivateLevel(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package ports
|
|
||||||
|
|
||||||
type ReferralStore interface {
|
|
||||||
// CreateReferralCode(ctx context.Context, referralCode domain.CreateReferralCode) (domain.ReferralCode, error)
|
|
||||||
// CreateUserReferral(ctx context.Context, referral domain.CreateUserReferrals) (domain.UserReferral, error)
|
|
||||||
// GetReferralCodesByUser(ctx context.Context, userID int64) ([]domain.ReferralCode, error)
|
|
||||||
// GetReferralCode(ctx context.Context, code string) (domain.ReferralCode, error)
|
|
||||||
// UpdateReferralCode(ctx context.Context, referral domain.UpdateReferralCode) error
|
|
||||||
// GetReferralStats(ctx context.Context, userID int64, companyID int64) (domain.ReferralStats, error)
|
|
||||||
// GetUserReferral(ctx context.Context, referredID int64) (domain.UserReferral, error)
|
|
||||||
// GetUserReferralsByCode(ctx context.Context, code string) ([]domain.UserReferral, error)
|
|
||||||
// GetUserReferralCount(ctx context.Context, referrerID int64) (int64, error)
|
|
||||||
}
|
|
||||||
834
internal/repository/course_catagories.go
Normal file
834
internal/repository/course_catagories.go
Normal file
|
|
@ -0,0 +1,834 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
dbgen "Yimaru-Backend/gen/db"
|
||||||
|
"Yimaru-Backend/internal/domain"
|
||||||
|
"Yimaru-Backend/internal/ports"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCourseStore(s *Store) ports.CourseStore { return s }
|
||||||
|
|
||||||
|
func (s *Store) CreateCourseCategory(ctx context.Context, name string) (domain.CourseCategory, error) {
|
||||||
|
tempCategory, err := s.queries.CreateCourseCategory(ctx, dbgen.CreateCourseCategoryParams{
|
||||||
|
Name: name,
|
||||||
|
IsActive: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.CourseCategory{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
category := domain.CourseCategory{
|
||||||
|
ID: tempCategory.ID,
|
||||||
|
Name: tempCategory.Name,
|
||||||
|
IsActive: tempCategory.IsActive,
|
||||||
|
CreatedAt: tempCategory.CreatedAt.Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetCourseCategoryByID(ctx context.Context, Id int64) (domain.CourseCategory, error) {
|
||||||
|
tempCategory, err := s.queries.GetCourseCategoryByID(ctx, Id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CourseCategory{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
category := domain.CourseCategory{
|
||||||
|
ID: tempCategory.ID,
|
||||||
|
Name: tempCategory.Name,
|
||||||
|
IsActive: tempCategory.IsActive,
|
||||||
|
CreatedAt: tempCategory.CreatedAt.Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListActiveCourseCategories(ctx context.Context) ([]domain.CourseCategory, error) {
|
||||||
|
rows, err := s.queries.ListActiveCourseCategories(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]domain.CourseCategory, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
result = append(result, domain.CourseCategory{
|
||||||
|
ID: r.ID,
|
||||||
|
Name: r.Name,
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
CreatedAt: r.CreatedAt.Time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateCourseCategory(ctx context.Context, id int64, name string, isActive bool) (domain.CourseCategory, error) {
|
||||||
|
row, err := s.queries.UpdateCourseCategory(ctx, dbgen.UpdateCourseCategoryParams{
|
||||||
|
ID: id,
|
||||||
|
Name: name,
|
||||||
|
IsActive: isActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.CourseCategory{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.CourseCategory{
|
||||||
|
ID: row.ID,
|
||||||
|
Name: row.Name,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
CreatedAt: row.CreatedAt.Time,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeactivateCourseCategory(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeactivateCourseCategory(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course related methods
|
||||||
|
func (s *Store) CreateCourse(ctx context.Context, c domain.Course) (domain.Course, error) {
|
||||||
|
row, err := s.queries.CreateCourse(ctx, dbgen.CreateCourseParams{
|
||||||
|
CategoryID: c.CategoryID,
|
||||||
|
Title: c.Title,
|
||||||
|
Description: pgtype.Text{String: c.Description, Valid: c.Description != ""},
|
||||||
|
IsActive: c.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Course{
|
||||||
|
ID: row.ID,
|
||||||
|
CategoryID: row.CategoryID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetCourseByID(ctx context.Context, id int64) (domain.Course, error) {
|
||||||
|
row, err := s.queries.GetCourseByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Course{
|
||||||
|
ID: row.ID,
|
||||||
|
CategoryID: row.CategoryID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListCoursesByCategory(ctx context.Context, categoryID int64) ([]domain.Course, error) {
|
||||||
|
rows, err := s.queries.ListCoursesByCategory(ctx, categoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]domain.Course, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.Course{
|
||||||
|
ID: r.ID,
|
||||||
|
CategoryID: r.CategoryID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListActiveCourses(ctx context.Context) ([]domain.Course, error) {
|
||||||
|
rows, err := s.queries.ListActiveCourses(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]domain.Course, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.Course{
|
||||||
|
ID: r.ID,
|
||||||
|
CategoryID: r.CategoryID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateCourse(ctx context.Context, c domain.Course) (domain.Course, error) {
|
||||||
|
row, err := s.queries.UpdateCourse(ctx, dbgen.UpdateCourseParams{
|
||||||
|
ID: c.ID,
|
||||||
|
CategoryID: c.CategoryID,
|
||||||
|
Title: c.Title,
|
||||||
|
Description: pgtype.Text{String: c.Description, Valid: c.Description != ""},
|
||||||
|
IsActive: c.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Course{
|
||||||
|
ID: row.ID,
|
||||||
|
CategoryID: row.CategoryID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeactivateCourse(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeactivateCourse(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program methods
|
||||||
|
func (s *Store) CreateProgram(ctx context.Context, p domain.Program) (domain.Program, error) {
|
||||||
|
row, err := s.queries.CreateProgram(ctx, dbgen.CreateProgramParams{
|
||||||
|
CourseID: p.CourseID,
|
||||||
|
Title: p.Title,
|
||||||
|
Description: pgtype.Text{String: p.Description, Valid: p.Description != ""},
|
||||||
|
Thumbnail: pgtype.Text{String: p.Thumbnail, Valid: p.Thumbnail != ""},
|
||||||
|
DisplayOrder: int32(p.Order),
|
||||||
|
IsActive: p.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Program{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Program{
|
||||||
|
ID: row.ID,
|
||||||
|
CourseID: row.CourseID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
Thumbnail: row.Thumbnail.String,
|
||||||
|
Order: int(row.DisplayOrder),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetProgramByID(ctx context.Context, id int64) (domain.Program, error) {
|
||||||
|
row, err := s.queries.GetProgramByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Program{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Program{
|
||||||
|
ID: row.ID,
|
||||||
|
CourseID: row.CourseID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
Thumbnail: row.Thumbnail.String,
|
||||||
|
Order: int(row.DisplayOrder),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListProgramsByCourse(ctx context.Context, courseID int64) ([]domain.Program, error) {
|
||||||
|
rows, err := s.queries.ListProgramsByCourse(ctx, courseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]domain.Program, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.Program{
|
||||||
|
ID: r.ID,
|
||||||
|
CourseID: r.CourseID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
Thumbnail: r.Thumbnail.String,
|
||||||
|
Order: int(r.DisplayOrder),
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListActivePrograms(ctx context.Context) ([]domain.Program, error) {
|
||||||
|
rows, err := s.queries.ListActivePrograms(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]domain.Program, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.Program{
|
||||||
|
ID: r.ID,
|
||||||
|
CourseID: r.CourseID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
Thumbnail: r.Thumbnail.String,
|
||||||
|
Order: int(r.DisplayOrder),
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateProgram(ctx context.Context, p domain.Program) (domain.Program, error) {
|
||||||
|
row, err := s.queries.UpdateProgram(ctx, dbgen.UpdateProgramParams{
|
||||||
|
ID: p.ID,
|
||||||
|
CourseID: p.CourseID,
|
||||||
|
Title: p.Title,
|
||||||
|
Description: pgtype.Text{String: p.Description, Valid: p.Description != ""},
|
||||||
|
Thumbnail: pgtype.Text{String: p.Thumbnail, Valid: p.Thumbnail != ""},
|
||||||
|
DisplayOrder: int32(p.Order),
|
||||||
|
IsActive: p.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Program{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Program{
|
||||||
|
ID: row.ID,
|
||||||
|
CourseID: row.CourseID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
Thumbnail: row.Thumbnail.String,
|
||||||
|
Order: int(row.DisplayOrder),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeactivateProgram(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeactivateProgram(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module methods
|
||||||
|
func (s *Store) CreateModule(ctx context.Context, m domain.Module) (domain.Module, error) {
|
||||||
|
row, err := s.queries.CreateModule(ctx, dbgen.CreateModuleParams{
|
||||||
|
LevelID: m.LevelID,
|
||||||
|
Title: m.Title,
|
||||||
|
Content: pgtype.Text{String: m.Content, Valid: m.Content != ""},
|
||||||
|
DisplayOrder: int32(m.Order),
|
||||||
|
IsActive: m.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Module{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Module{
|
||||||
|
ID: row.ID,
|
||||||
|
LevelID: row.LevelID,
|
||||||
|
Title: row.Title,
|
||||||
|
Content: row.Content.String,
|
||||||
|
Order: int(row.DisplayOrder),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetModuleByID(ctx context.Context, id int64) (domain.Module, error) {
|
||||||
|
row, err := s.queries.GetModuleByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Module{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Module{
|
||||||
|
ID: row.ID,
|
||||||
|
LevelID: row.LevelID,
|
||||||
|
Title: row.Title,
|
||||||
|
Content: row.Content.String,
|
||||||
|
Order: int(row.DisplayOrder),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListModulesByLevel(ctx context.Context, levelID int64) ([]domain.Module, error) {
|
||||||
|
rows, err := s.queries.ListModulesByLevel(ctx, levelID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]domain.Module, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.Module{
|
||||||
|
ID: r.ID,
|
||||||
|
LevelID: r.LevelID,
|
||||||
|
Title: r.Title,
|
||||||
|
Content: r.Content.String,
|
||||||
|
Order: int(r.DisplayOrder),
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateModule(ctx context.Context, m domain.Module) (domain.Module, error) {
|
||||||
|
row, err := s.queries.UpdateModule(ctx, dbgen.UpdateModuleParams{
|
||||||
|
ID: m.ID,
|
||||||
|
Title: m.Title,
|
||||||
|
Content: pgtype.Text{String: m.Content, Valid: m.Content != ""},
|
||||||
|
DisplayOrder: int32(m.Order),
|
||||||
|
IsActive: m.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Module{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Module{
|
||||||
|
ID: row.ID,
|
||||||
|
LevelID: row.LevelID,
|
||||||
|
Title: row.Title,
|
||||||
|
Content: row.Content.String,
|
||||||
|
Order: int(row.DisplayOrder),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeactivateModule(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeactivateModule(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module video methods
|
||||||
|
func (s *Store) CreateModuleVideo(ctx context.Context, v domain.ModuleVideo) (domain.ModuleVideo, error) {
|
||||||
|
row, err := s.queries.CreateModuleVideo(ctx, dbgen.CreateModuleVideoParams{
|
||||||
|
ModuleID: v.ModuleID,
|
||||||
|
Title: v.Title,
|
||||||
|
Description: pgtype.Text{String: v.Description, Valid: v.Description != ""},
|
||||||
|
VideoUrl: v.VideoURL,
|
||||||
|
Duration: int32(v.Duration),
|
||||||
|
Resolution: pgtype.Text{String: v.Resolution, Valid: v.Resolution != ""},
|
||||||
|
IsPublished: v.PublishSettings.IsPublished,
|
||||||
|
PublishDate: pgtype.Timestamptz{Time: v.PublishSettings.PublishDate, Valid: !v.PublishSettings.PublishDate.IsZero()},
|
||||||
|
Visibility: pgtype.Text{String: v.PublishSettings.Visibility, Valid: v.PublishSettings.Visibility != ""},
|
||||||
|
InstructorID: pgtype.Text{String: v.InstructorId, Valid: v.InstructorId != ""},
|
||||||
|
Thumbnail: pgtype.Text{String: v.Thumbnail, Valid: v.Thumbnail != ""},
|
||||||
|
IsActive: v.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.ModuleVideo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.ModuleVideo{
|
||||||
|
ID: row.ID,
|
||||||
|
ModuleID: row.ModuleID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
VideoURL: row.VideoUrl,
|
||||||
|
Duration: int(row.Duration),
|
||||||
|
Resolution: row.Resolution.String,
|
||||||
|
PublishSettings: domain.PublishSettings{
|
||||||
|
IsPublished: row.IsPublished,
|
||||||
|
PublishDate: row.PublishDate.Time,
|
||||||
|
Visibility: row.Visibility.String,
|
||||||
|
},
|
||||||
|
InstructorId: row.InstructorID.String,
|
||||||
|
Thumbnail: row.Thumbnail.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetModuleVideoByID(ctx context.Context, id int64) (domain.ModuleVideo, error) {
|
||||||
|
row, err := s.queries.GetModuleVideoByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ModuleVideo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.ModuleVideo{
|
||||||
|
ID: row.ID,
|
||||||
|
ModuleID: row.ModuleID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
VideoURL: row.VideoUrl,
|
||||||
|
Duration: int(row.Duration),
|
||||||
|
Resolution: row.Resolution.String,
|
||||||
|
PublishSettings: domain.PublishSettings{
|
||||||
|
IsPublished: row.IsPublished,
|
||||||
|
PublishDate: row.PublishDate.Time,
|
||||||
|
Visibility: row.Visibility.String,
|
||||||
|
},
|
||||||
|
InstructorId: row.InstructorID.String,
|
||||||
|
Thumbnail: row.Thumbnail.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListAllVideosByModule(ctx context.Context, moduleID int64) ([]domain.ModuleVideo, error) {
|
||||||
|
rows, err := s.queries.ListAllVideosByModule(ctx, moduleID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]domain.ModuleVideo, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.ModuleVideo{
|
||||||
|
ID: r.ID,
|
||||||
|
ModuleID: r.ModuleID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
VideoURL: r.VideoUrl,
|
||||||
|
Duration: int(r.Duration),
|
||||||
|
Resolution: r.Resolution.String,
|
||||||
|
PublishSettings: domain.PublishSettings{
|
||||||
|
IsPublished: r.IsPublished,
|
||||||
|
PublishDate: r.PublishDate.Time,
|
||||||
|
Visibility: r.Visibility.String,
|
||||||
|
},
|
||||||
|
InstructorId: r.InstructorID.String,
|
||||||
|
Thumbnail: r.Thumbnail.String,
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListPublishedVideosByModule(ctx context.Context, moduleID int64) ([]domain.ModuleVideo, error) {
|
||||||
|
rows, err := s.queries.ListPublishedVideosByModule(ctx, moduleID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]domain.ModuleVideo, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.ModuleVideo{
|
||||||
|
ID: r.ID,
|
||||||
|
ModuleID: r.ModuleID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
VideoURL: r.VideoUrl,
|
||||||
|
Duration: int(r.Duration),
|
||||||
|
Resolution: r.Resolution.String,
|
||||||
|
PublishSettings: domain.PublishSettings{
|
||||||
|
IsPublished: true,
|
||||||
|
PublishDate: r.PublishDate.Time,
|
||||||
|
Visibility: r.Visibility.String,
|
||||||
|
},
|
||||||
|
InstructorId: r.InstructorID.String,
|
||||||
|
Thumbnail: r.Thumbnail.String,
|
||||||
|
IsActive: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateModuleVideo(ctx context.Context, v domain.ModuleVideo) (domain.ModuleVideo, error) {
|
||||||
|
row, err := s.queries.UpdateModuleVideo(ctx, dbgen.UpdateModuleVideoParams{
|
||||||
|
ID: v.ID,
|
||||||
|
Title: v.Title,
|
||||||
|
Description: pgtype.Text{String: v.Description, Valid: v.Description != ""},
|
||||||
|
VideoUrl: v.VideoURL,
|
||||||
|
Duration: int32(v.Duration),
|
||||||
|
Resolution: pgtype.Text{String: v.Resolution, Valid: v.Resolution != ""},
|
||||||
|
IsPublished: v.PublishSettings.IsPublished,
|
||||||
|
PublishDate: pgtype.Timestamptz{Time: v.PublishSettings.PublishDate, Valid: !v.PublishSettings.PublishDate.IsZero()},
|
||||||
|
Visibility: pgtype.Text{String: v.PublishSettings.Visibility, Valid: v.PublishSettings.Visibility != ""},
|
||||||
|
InstructorID: pgtype.Text{String: v.InstructorId, Valid: v.InstructorId != ""},
|
||||||
|
Thumbnail: pgtype.Text{String: v.Thumbnail, Valid: v.Thumbnail != ""},
|
||||||
|
IsActive: v.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.ModuleVideo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.ModuleVideo{
|
||||||
|
ID: row.ID,
|
||||||
|
ModuleID: row.ModuleID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
VideoURL: row.VideoUrl,
|
||||||
|
Duration: int(row.Duration),
|
||||||
|
Resolution: row.Resolution.String,
|
||||||
|
PublishSettings: domain.PublishSettings{
|
||||||
|
IsPublished: row.IsPublished,
|
||||||
|
PublishDate: row.PublishDate.Time,
|
||||||
|
Visibility: row.Visibility.String,
|
||||||
|
},
|
||||||
|
InstructorId: row.InstructorID.String,
|
||||||
|
Thumbnail: row.Thumbnail.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeactivateModuleVideo(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeactivateModuleVideo(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Practices and practice question methods
|
||||||
|
func (s *Store) CreatePractice(ctx context.Context, p domain.Practice) (domain.Practice, error) {
|
||||||
|
row, err := s.queries.CreatePractice(ctx, dbgen.CreatePracticeParams{
|
||||||
|
OwnerType: p.OwnerType,
|
||||||
|
OwnerID: p.OwnerID,
|
||||||
|
Title: p.Title,
|
||||||
|
Description: pgtype.Text{String: p.Description, Valid: p.Description != ""},
|
||||||
|
BannerImage: pgtype.Text{String: p.BannerImage, Valid: p.BannerImage != ""},
|
||||||
|
Persona: pgtype.Text{String: p.Persona, Valid: p.Persona != ""},
|
||||||
|
IsActive: p.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Practice{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Practice{
|
||||||
|
ID: row.ID,
|
||||||
|
OwnerType: row.OwnerType,
|
||||||
|
OwnerID: row.OwnerID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
BannerImage: row.BannerImage.String,
|
||||||
|
Persona: row.Persona.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetPracticeByID(ctx context.Context, id int64) (domain.Practice, error) {
|
||||||
|
row, err := s.queries.GetPracticeByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Practice{}, err
|
||||||
|
}
|
||||||
|
return domain.Practice{
|
||||||
|
ID: row.ID,
|
||||||
|
OwnerType: row.OwnerType,
|
||||||
|
OwnerID: row.OwnerID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
BannerImage: row.BannerImage.String,
|
||||||
|
Persona: row.Persona.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListPracticesByOwner(ctx context.Context, ownerType string, ownerID int64) ([]domain.Practice, error) {
|
||||||
|
rows, err := s.queries.ListPracticesByOwner(ctx, dbgen.ListPracticesByOwnerParams{OwnerType: ownerType, OwnerID: ownerID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := make([]domain.Practice, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.Practice{
|
||||||
|
ID: r.ID,
|
||||||
|
OwnerType: r.OwnerType,
|
||||||
|
OwnerID: r.OwnerID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
BannerImage: r.BannerImage.String,
|
||||||
|
Persona: r.Persona.String,
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdatePractice(ctx context.Context, p domain.Practice) (domain.Practice, error) {
|
||||||
|
row, err := s.queries.UpdatePractice(ctx, dbgen.UpdatePracticeParams{
|
||||||
|
ID: p.ID,
|
||||||
|
Title: p.Title,
|
||||||
|
Description: pgtype.Text{String: p.Description, Valid: p.Description != ""},
|
||||||
|
BannerImage: pgtype.Text{String: p.BannerImage, Valid: p.BannerImage != ""},
|
||||||
|
Persona: pgtype.Text{String: p.Persona, Valid: p.Persona != ""},
|
||||||
|
IsActive: p.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Practice{}, err
|
||||||
|
}
|
||||||
|
return domain.Practice{
|
||||||
|
ID: row.ID,
|
||||||
|
OwnerType: row.OwnerType,
|
||||||
|
OwnerID: row.OwnerID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
BannerImage: row.BannerImage.String,
|
||||||
|
Persona: row.Persona.String,
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeactivatePractice(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeactivatePractice(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Practice question methods
|
||||||
|
func (s *Store) CreatePracticeQuestion(ctx context.Context, qn domain.PracticeQuestion) (domain.PracticeQuestion, error) {
|
||||||
|
row, err := s.queries.CreatePracticeQuestion(ctx, dbgen.CreatePracticeQuestionParams{
|
||||||
|
PracticeID: qn.PracticeID,
|
||||||
|
Question: qn.Question,
|
||||||
|
QuestionVoicePrompt: pgtype.Text{String: qn.QuestionVoicePrompt, Valid: qn.QuestionVoicePrompt != ""},
|
||||||
|
SampleAnswerVoicePrompt: pgtype.Text{String: qn.SampleAnswerVoicePrompt, Valid: qn.SampleAnswerVoicePrompt != ""},
|
||||||
|
SampleAnswer: pgtype.Text{String: qn.SampleAnswer, Valid: qn.SampleAnswer != ""},
|
||||||
|
Tips: pgtype.Text{String: qn.Tips, Valid: qn.Tips != ""},
|
||||||
|
Type: qn.Type,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.PracticeQuestion{}, err
|
||||||
|
}
|
||||||
|
return domain.PracticeQuestion{
|
||||||
|
ID: row.ID,
|
||||||
|
PracticeID: row.PracticeID,
|
||||||
|
Question: row.Question,
|
||||||
|
QuestionVoicePrompt: row.QuestionVoicePrompt.String,
|
||||||
|
SampleAnswerVoicePrompt: row.SampleAnswerVoicePrompt.String,
|
||||||
|
SampleAnswer: row.SampleAnswer.String,
|
||||||
|
Tips: row.Tips.String,
|
||||||
|
Type: row.Type,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetPracticeQuestionByID(ctx context.Context, id int64) (domain.PracticeQuestion, error) {
|
||||||
|
row, err := s.queries.GetPracticeQuestionByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PracticeQuestion{}, err
|
||||||
|
}
|
||||||
|
return domain.PracticeQuestion{
|
||||||
|
ID: row.ID,
|
||||||
|
PracticeID: row.PracticeID,
|
||||||
|
Question: row.Question,
|
||||||
|
QuestionVoicePrompt: row.QuestionVoicePrompt.String,
|
||||||
|
SampleAnswerVoicePrompt: row.SampleAnswerVoicePrompt.String,
|
||||||
|
SampleAnswer: row.SampleAnswer.String,
|
||||||
|
Tips: row.Tips.String,
|
||||||
|
Type: row.Type,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListPracticeQuestions(ctx context.Context, practiceID int64) ([]domain.PracticeQuestion, error) {
|
||||||
|
rows, err := s.queries.ListPracticeQuestions(ctx, practiceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := make([]domain.PracticeQuestion, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.PracticeQuestion{
|
||||||
|
ID: r.ID,
|
||||||
|
PracticeID: r.PracticeID,
|
||||||
|
Question: r.Question,
|
||||||
|
QuestionVoicePrompt: r.QuestionVoicePrompt.String,
|
||||||
|
SampleAnswerVoicePrompt: r.SampleAnswerVoicePrompt.String,
|
||||||
|
SampleAnswer: r.SampleAnswer.String,
|
||||||
|
Tips: r.Tips.String,
|
||||||
|
Type: r.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdatePracticeQuestion(ctx context.Context, qn domain.PracticeQuestion) (domain.PracticeQuestion, error) {
|
||||||
|
row, err := s.queries.UpdatePracticeQuestion(ctx, dbgen.UpdatePracticeQuestionParams{
|
||||||
|
ID: qn.ID,
|
||||||
|
Question: qn.Question,
|
||||||
|
QuestionVoicePrompt: pgtype.Text{String: qn.QuestionVoicePrompt, Valid: qn.QuestionVoicePrompt != ""},
|
||||||
|
SampleAnswerVoicePrompt: pgtype.Text{String: qn.SampleAnswerVoicePrompt, Valid: qn.SampleAnswerVoicePrompt != ""},
|
||||||
|
SampleAnswer: pgtype.Text{String: qn.SampleAnswer, Valid: qn.SampleAnswer != ""},
|
||||||
|
Tips: pgtype.Text{String: qn.Tips, Valid: qn.Tips != ""},
|
||||||
|
Type: qn.Type,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.PracticeQuestion{}, err
|
||||||
|
}
|
||||||
|
return domain.PracticeQuestion{
|
||||||
|
ID: row.ID,
|
||||||
|
PracticeID: row.PracticeID,
|
||||||
|
Question: row.Question,
|
||||||
|
QuestionVoicePrompt: row.QuestionVoicePrompt.String,
|
||||||
|
SampleAnswerVoicePrompt: row.SampleAnswerVoicePrompt.String,
|
||||||
|
SampleAnswer: row.SampleAnswer.String,
|
||||||
|
Tips: row.Tips.String,
|
||||||
|
Type: row.Type,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeletePracticeQuestion(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeletePracticeQuestion(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level (program level) methods
|
||||||
|
func (s *Store) CreateLevel(ctx context.Context, l domain.Level) (domain.Level, error) {
|
||||||
|
row, err := s.queries.CreateLevel(ctx, dbgen.CreateLevelParams{
|
||||||
|
ProgramID: l.ProgramID,
|
||||||
|
Title: l.Title,
|
||||||
|
Description: pgtype.Text{String: l.Description, Valid: l.Description != ""},
|
||||||
|
LevelIndex: int32(l.LevelIndex),
|
||||||
|
NumberOfModules: int32(l.NumberOfModules),
|
||||||
|
NumberOfPractices: int32(l.NumberOfPractices),
|
||||||
|
NumberOfVideos: int32(l.NumberOfVideos),
|
||||||
|
IsActive: l.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Level{}, err
|
||||||
|
}
|
||||||
|
return domain.Level{
|
||||||
|
ID: row.ID,
|
||||||
|
ProgramID: row.ProgramID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
LevelIndex: int(row.LevelIndex),
|
||||||
|
NumberOfModules: int(row.NumberOfModules),
|
||||||
|
NumberOfPractices: int(row.NumberOfPractices),
|
||||||
|
NumberOfVideos: int(row.NumberOfVideos),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetLevelByID(ctx context.Context, id int64) (domain.Level, error) {
|
||||||
|
row, err := s.queries.GetLevelByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Level{}, err
|
||||||
|
}
|
||||||
|
return domain.Level{
|
||||||
|
ID: row.ID,
|
||||||
|
ProgramID: row.ProgramID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
LevelIndex: int(row.LevelIndex),
|
||||||
|
NumberOfModules: int(row.NumberOfModules),
|
||||||
|
NumberOfPractices: int(row.NumberOfPractices),
|
||||||
|
NumberOfVideos: int(row.NumberOfVideos),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListLevelsByProgram(ctx context.Context, programID int64) ([]domain.Level, error) {
|
||||||
|
rows, err := s.queries.ListLevelsByProgram(ctx, programID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := make([]domain.Level, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
res = append(res, domain.Level{
|
||||||
|
ID: r.ID,
|
||||||
|
ProgramID: r.ProgramID,
|
||||||
|
Title: r.Title,
|
||||||
|
Description: r.Description.String,
|
||||||
|
LevelIndex: int(r.LevelIndex),
|
||||||
|
NumberOfModules: int(r.NumberOfModules),
|
||||||
|
NumberOfPractices: int(r.NumberOfPractices),
|
||||||
|
NumberOfVideos: int(r.NumberOfVideos),
|
||||||
|
IsActive: r.IsActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateLevel(ctx context.Context, l domain.Level) (domain.Level, error) {
|
||||||
|
row, err := s.queries.UpdateLevel(ctx, dbgen.UpdateLevelParams{
|
||||||
|
ID: l.ID,
|
||||||
|
Title: l.Title,
|
||||||
|
Description: pgtype.Text{String: l.Description, Valid: l.Description != ""},
|
||||||
|
LevelIndex: int32(l.LevelIndex),
|
||||||
|
NumberOfModules: int32(l.NumberOfModules),
|
||||||
|
NumberOfPractices: int32(l.NumberOfPractices),
|
||||||
|
NumberOfVideos: int32(l.NumberOfVideos),
|
||||||
|
IsActive: l.IsActive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Level{}, err
|
||||||
|
}
|
||||||
|
return domain.Level{
|
||||||
|
ID: row.ID,
|
||||||
|
ProgramID: row.ProgramID,
|
||||||
|
Title: row.Title,
|
||||||
|
Description: row.Description.String,
|
||||||
|
LevelIndex: int(row.LevelIndex),
|
||||||
|
NumberOfModules: int(row.NumberOfModules),
|
||||||
|
NumberOfPractices: int(row.NumberOfPractices),
|
||||||
|
NumberOfVideos: int(row.NumberOfVideos),
|
||||||
|
IsActive: row.IsActive,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeactivateLevel(ctx context.Context, id int64) error {
|
||||||
|
return s.queries.DeactivateLevel(ctx, id)
|
||||||
|
}
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"Yimaru-Backend/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CurrencyRepository interface {
|
|
||||||
GetExchangeRate(ctx context.Context, from, to domain.IntCurrency) (domain.IntCurrencyRate, error)
|
|
||||||
StoreExchangeRate(ctx context.Context, rate domain.IntCurrencyRate) error
|
|
||||||
GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CurrencyPostgresRepository struct {
|
|
||||||
store *Store
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCurrencyPostgresRepository(store *Store) *CurrencyPostgresRepository {
|
|
||||||
return &CurrencyPostgresRepository{store: store}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CurrencyPostgresRepository) GetExchangeRate(ctx context.Context, from, to domain.IntCurrency) (domain.IntCurrencyRate, error) {
|
|
||||||
const query = `
|
|
||||||
SELECT from_currency, to_currency, rate, precision, valid_until
|
|
||||||
FROM exchange_rates
|
|
||||||
WHERE from_currency = $1 AND to_currency = $2 AND valid_until > NOW()
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1`
|
|
||||||
|
|
||||||
var rate domain.IntCurrencyRate
|
|
||||||
err := r.store.conn.QueryRow(ctx, query, from, to).Scan(
|
|
||||||
&rate.From,
|
|
||||||
&rate.To,
|
|
||||||
&rate.Rate,
|
|
||||||
&rate.ValidUntil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return domain.IntCurrencyRate{}, fmt.Errorf("%w: no rate found for %s to %s",
|
|
||||||
domain.ErrIntCurrencyConversion, from, to)
|
|
||||||
}
|
|
||||||
return domain.IntCurrencyRate{}, fmt.Errorf("failed to get exchange rate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rate, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CurrencyPostgresRepository) StoreExchangeRate(ctx context.Context, rate domain.IntCurrencyRate) error {
|
|
||||||
const query = `
|
|
||||||
INSERT INTO exchange_rates (from_currency, to_currency, rate, precision, valid_until)
|
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
|
||||||
ON CONFLICT (from_currency, to_currency)
|
|
||||||
DO UPDATE SET
|
|
||||||
rate = EXCLUDED.rate,
|
|
||||||
precision = EXCLUDED.precision,
|
|
||||||
valid_until = EXCLUDED.valid_until,
|
|
||||||
created_at = NOW()`
|
|
||||||
|
|
||||||
_, err := r.store.conn.Exec(ctx, query,
|
|
||||||
rate.From,
|
|
||||||
rate.To,
|
|
||||||
rate.Rate,
|
|
||||||
rate.ValidUntil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to store exchange rate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CurrencyPostgresRepository) GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error) {
|
|
||||||
const query = `SELECT DISTINCT currency FROM supported_currencies ORDER BY currency`
|
|
||||||
|
|
||||||
var currencies []domain.IntCurrency
|
|
||||||
rows, err := r.store.conn.Query(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get supported currencies: %w", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var currency domain.IntCurrency
|
|
||||||
if err := rows.Scan(¤cy); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan currency: %w", err)
|
|
||||||
}
|
|
||||||
currencies = append(currencies, currency)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("row iteration error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return currencies, nil
|
|
||||||
}
|
|
||||||
214
internal/services/course_management/service.go
Normal file
214
internal/services/course_management/service.go
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
package course_management
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"Yimaru-Backend/internal/config"
|
||||||
|
"Yimaru-Backend/internal/domain"
|
||||||
|
"Yimaru-Backend/internal/ports"
|
||||||
|
notificationservice "Yimaru-Backend/internal/services/notification"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
userStore ports.UserStore
|
||||||
|
courseStore ports.CourseStore
|
||||||
|
notificationSvc *notificationservice.Service
|
||||||
|
// messengerSvc *messenger.Service
|
||||||
|
config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(
|
||||||
|
userStore ports.UserStore,
|
||||||
|
courseStore ports.CourseStore,
|
||||||
|
notificationSvc *notificationservice.Service,
|
||||||
|
// messengerSvc *messenger.Service,
|
||||||
|
cfg *config.Config,
|
||||||
|
) *Service {
|
||||||
|
return &Service{
|
||||||
|
userStore: userStore,
|
||||||
|
courseStore: courseStore,
|
||||||
|
notificationSvc: notificationSvc,
|
||||||
|
// messengerSvc: messengerSvc,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course category methods
|
||||||
|
func (s *Service) CreateCourseCategory(ctx context.Context, name string) (domain.CourseCategory, error) {
|
||||||
|
return s.courseStore.CreateCourseCategory(ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetCourseCategoryByID(ctx context.Context, id int64) (domain.CourseCategory, error) {
|
||||||
|
return s.courseStore.GetCourseCategoryByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListActiveCourseCategories(ctx context.Context) ([]domain.CourseCategory, error) {
|
||||||
|
return s.courseStore.ListActiveCourseCategories(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateCourseCategory(ctx context.Context, id int64, name string, isActive bool) (domain.CourseCategory, error) {
|
||||||
|
return s.courseStore.UpdateCourseCategory(ctx, id, name, isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeactivateCourseCategory(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeactivateCourseCategory(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Courses
|
||||||
|
func (s *Service) CreateCourse(ctx context.Context, c domain.Course) (domain.Course, error) {
|
||||||
|
return s.courseStore.CreateCourse(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetCourseByID(ctx context.Context, id int64) (domain.Course, error) {
|
||||||
|
return s.courseStore.GetCourseByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListCoursesByCategory(ctx context.Context, categoryID int64) ([]domain.Course, error) {
|
||||||
|
return s.courseStore.ListCoursesByCategory(ctx, categoryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListActiveCourses(ctx context.Context) ([]domain.Course, error) {
|
||||||
|
return s.courseStore.ListActiveCourses(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateCourse(ctx context.Context, c domain.Course) (domain.Course, error) {
|
||||||
|
return s.courseStore.UpdateCourse(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeactivateCourse(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeactivateCourse(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Programs
|
||||||
|
func (s *Service) CreateProgram(ctx context.Context, p domain.Program) (domain.Program, error) {
|
||||||
|
return s.courseStore.CreateProgram(ctx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetProgramByID(ctx context.Context, id int64) (domain.Program, error) {
|
||||||
|
return s.courseStore.GetProgramByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListProgramsByCourse(ctx context.Context, courseID int64) ([]domain.Program, error) {
|
||||||
|
return s.courseStore.ListProgramsByCourse(ctx, courseID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListActivePrograms(ctx context.Context) ([]domain.Program, error) {
|
||||||
|
return s.courseStore.ListActivePrograms(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateProgram(ctx context.Context, p domain.Program) (domain.Program, error) {
|
||||||
|
return s.courseStore.UpdateProgram(ctx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeactivateProgram(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeactivateProgram(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modules
|
||||||
|
func (s *Service) CreateModule(ctx context.Context, m domain.Module) (domain.Module, error) {
|
||||||
|
return s.courseStore.CreateModule(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetModuleByID(ctx context.Context, id int64) (domain.Module, error) {
|
||||||
|
return s.courseStore.GetModuleByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListModulesByLevel(ctx context.Context, levelID int64) ([]domain.Module, error) {
|
||||||
|
return s.courseStore.ListModulesByLevel(ctx, levelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateModule(ctx context.Context, m domain.Module) (domain.Module, error) {
|
||||||
|
return s.courseStore.UpdateModule(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeactivateModule(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeactivateModule(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module videos
|
||||||
|
func (s *Service) CreateModuleVideo(ctx context.Context, v domain.ModuleVideo) (domain.ModuleVideo, error) {
|
||||||
|
return s.courseStore.CreateModuleVideo(ctx, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetModuleVideoByID(ctx context.Context, id int64) (domain.ModuleVideo, error) {
|
||||||
|
return s.courseStore.GetModuleVideoByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListAllVideosByModule(ctx context.Context, moduleID int64) ([]domain.ModuleVideo, error) {
|
||||||
|
return s.courseStore.ListAllVideosByModule(ctx, moduleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListPublishedVideosByModule(ctx context.Context, moduleID int64) ([]domain.ModuleVideo, error) {
|
||||||
|
return s.courseStore.ListPublishedVideosByModule(ctx, moduleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateModuleVideo(ctx context.Context, v domain.ModuleVideo) (domain.ModuleVideo, error) {
|
||||||
|
return s.courseStore.UpdateModuleVideo(ctx, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeactivateModuleVideo(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeactivateModuleVideo(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Practices
|
||||||
|
func (s *Service) CreatePractice(ctx context.Context, p domain.Practice) (domain.Practice, error) {
|
||||||
|
return s.courseStore.CreatePractice(ctx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetPracticeByID(ctx context.Context, id int64) (domain.Practice, error) {
|
||||||
|
return s.courseStore.GetPracticeByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListPracticesByOwner(ctx context.Context, ownerType string, ownerID int64) ([]domain.Practice, error) {
|
||||||
|
return s.courseStore.ListPracticesByOwner(ctx, ownerType, ownerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdatePractice(ctx context.Context, p domain.Practice) (domain.Practice, error) {
|
||||||
|
return s.courseStore.UpdatePractice(ctx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeactivatePractice(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeactivatePractice(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Practice questions
|
||||||
|
func (s *Service) CreatePracticeQuestion(ctx context.Context, qn domain.PracticeQuestion) (domain.PracticeQuestion, error) {
|
||||||
|
return s.courseStore.CreatePracticeQuestion(ctx, qn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetPracticeQuestionByID(ctx context.Context, id int64) (domain.PracticeQuestion, error) {
|
||||||
|
return s.courseStore.GetPracticeQuestionByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListPracticeQuestions(ctx context.Context, practiceID int64) ([]domain.PracticeQuestion, error) {
|
||||||
|
return s.courseStore.ListPracticeQuestions(ctx, practiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdatePracticeQuestion(ctx context.Context, qn domain.PracticeQuestion) (domain.PracticeQuestion, error) {
|
||||||
|
return s.courseStore.UpdatePracticeQuestion(ctx, qn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeletePracticeQuestion(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeletePracticeQuestion(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels
|
||||||
|
func (s *Service) CreateLevel(ctx context.Context, l domain.Level) (domain.Level, error) {
|
||||||
|
return s.courseStore.CreateLevel(ctx, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetLevelByID(ctx context.Context, id int64) (domain.Level, error) {
|
||||||
|
return s.courseStore.GetLevelByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListLevelsByProgram(ctx context.Context, programID int64) ([]domain.Level, error) {
|
||||||
|
return s.courseStore.ListLevelsByProgram(ctx, programID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateLevel(ctx context.Context, l domain.Level) (domain.Level, error) {
|
||||||
|
return s.courseStore.UpdateLevel(ctx, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeactivateLevel(ctx context.Context, id int64) error {
|
||||||
|
return s.courseStore.DeactivateLevel(ctx, id)
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"Yimaru-Backend/internal/services/arifpay"
|
"Yimaru-Backend/internal/services/arifpay"
|
||||||
"Yimaru-Backend/internal/services/assessment"
|
"Yimaru-Backend/internal/services/assessment"
|
||||||
"Yimaru-Backend/internal/services/authentication"
|
"Yimaru-Backend/internal/services/authentication"
|
||||||
|
"Yimaru-Backend/internal/services/course_management"
|
||||||
issuereporting "Yimaru-Backend/internal/services/issue_reporting"
|
issuereporting "Yimaru-Backend/internal/services/issue_reporting"
|
||||||
notificationservice "Yimaru-Backend/internal/services/notification"
|
notificationservice "Yimaru-Backend/internal/services/notification"
|
||||||
"Yimaru-Backend/internal/services/recommendation"
|
"Yimaru-Backend/internal/services/recommendation"
|
||||||
|
|
@ -26,6 +27,7 @@ import (
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
assessmentSvc *assessment.Service
|
assessmentSvc *assessment.Service
|
||||||
|
courseSvc *course_management.Service
|
||||||
arifpaySvc *arifpay.ArifpayService
|
arifpaySvc *arifpay.ArifpayService
|
||||||
issueReportingSvc *issuereporting.Service
|
issueReportingSvc *issuereporting.Service
|
||||||
fiber *fiber.App
|
fiber *fiber.App
|
||||||
|
|
@ -46,6 +48,7 @@ type App struct {
|
||||||
|
|
||||||
func NewApp(
|
func NewApp(
|
||||||
assessmentSvc *assessment.Service,
|
assessmentSvc *assessment.Service,
|
||||||
|
courseSvc *course_management.Service,
|
||||||
arifpaySvc *arifpay.ArifpayService,
|
arifpaySvc *arifpay.ArifpayService,
|
||||||
issueReportingSvc *issuereporting.Service,
|
issueReportingSvc *issuereporting.Service,
|
||||||
port int, validator *customvalidator.CustomValidator,
|
port int, validator *customvalidator.CustomValidator,
|
||||||
|
|
@ -78,6 +81,7 @@ func NewApp(
|
||||||
|
|
||||||
s := &App{
|
s := &App{
|
||||||
assessmentSvc: assessmentSvc,
|
assessmentSvc: assessmentSvc,
|
||||||
|
courseSvc: courseSvc,
|
||||||
arifpaySvc: arifpaySvc,
|
arifpaySvc: arifpaySvc,
|
||||||
// issueReportingSvc: issueReportingSvc,
|
// issueReportingSvc: issueReportingSvc,
|
||||||
fiber: app,
|
fiber: app,
|
||||||
|
|
|
||||||
546
internal/web_server/handlers/course_management.go
Normal file
546
internal/web_server/handlers/course_management.go
Normal file
|
|
@ -0,0 +1,546 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"Yimaru-Backend/internal/domain"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateCourseCategory godoc
|
||||||
|
// @Summary Create course category
|
||||||
|
// @Description Creates a new course category
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param category body domain.CourseCategory true "Course category payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.CourseCategory}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/course-categories [post]
|
||||||
|
func (h *Handler) CreateCourseCategory(c *fiber.Ctx) error {
|
||||||
|
var req domain.CourseCategory
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid request body",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cat, err := h.courseMgmtSvc.CreateCourseCategory(c.Context(), req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to create course category",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||||
|
Message: "Course category created successfully",
|
||||||
|
Data: cat,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCourseCategoryByID godoc
|
||||||
|
// @Summary Get course category
|
||||||
|
// @Description Get course category by ID
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Category ID"
|
||||||
|
// @Success 200 {object} domain.Response{data=domain.CourseCategory}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 404 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/course-categories/{id} [get]
|
||||||
|
func (h *Handler) GetCourseCategoryByID(c *fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid category ID",
|
||||||
|
Error: "ID must be a positive integer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cat, err := h.courseMgmtSvc.GetCourseCategoryByID(c.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch course category",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Course category fetched successfully",
|
||||||
|
Data: cat,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListActiveCourseCategories godoc
|
||||||
|
// @Summary List active course categories
|
||||||
|
// @Description Returns all active course categories
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.CourseCategory}
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/course-categories [get]
|
||||||
|
func (h *Handler) ListActiveCourseCategories(c *fiber.Ctx) error {
|
||||||
|
cats, err := h.courseMgmtSvc.ListActiveCourseCategories(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch course categories",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Course categories fetched successfully",
|
||||||
|
Data: cats,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCourseCategory godoc
|
||||||
|
// @Summary Update course category
|
||||||
|
// @Description Updates a course category
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Category ID"
|
||||||
|
// @Param category body domain.CourseCategory true "Course category payload"
|
||||||
|
// @Success 200 {object} domain.Response{data=domain.CourseCategory}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/course-categories/{id} [put]
|
||||||
|
func (h *Handler) UpdateCourseCategory(c *fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid category ID",
|
||||||
|
Error: "ID must be a positive integer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var req domain.CourseCategory
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid request body",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.courseMgmtSvc.UpdateCourseCategory(c.Context(), id, req.Name, req.IsActive)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to update course category",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Course category updated successfully",
|
||||||
|
Data: updated,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeactivateCourseCategory godoc
|
||||||
|
// @Summary Deactivate course category
|
||||||
|
// @Description Deactivates a course category
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Category ID"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/course-categories/{id}/deactivate [post]
|
||||||
|
func (h *Handler) DeactivateCourseCategory(c *fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid category ID",
|
||||||
|
Error: "ID must be a positive integer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.courseMgmtSvc.DeactivateCourseCategory(c.Context(), id); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to deactivate course category",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Course category deactivated",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Courses handlers ---
|
||||||
|
|
||||||
|
// CreateCourse godoc
|
||||||
|
// @Summary Create course
|
||||||
|
// @Description Creates a new course
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param course body domain.Course true "Course payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.Course}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/courses [post]
|
||||||
|
func (h *Handler) CreateCourse(c *fiber.Ctx) error {
|
||||||
|
var req domain.Course
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid request body",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
course, err := h.courseMgmtSvc.CreateCourse(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to create course",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||||
|
Message: "Course created successfully",
|
||||||
|
Data: course,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCourseByID godoc
|
||||||
|
// @Summary Get course
|
||||||
|
// @Description Get course by ID
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Course ID"
|
||||||
|
// @Success 200 {object} domain.Response{data=domain.Course}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 404 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/courses/{id} [get]
|
||||||
|
func (h *Handler) GetCourseByID(c *fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid course ID",
|
||||||
|
Error: "ID must be a positive integer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
course, err := h.courseMgmtSvc.GetCourseByID(c.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch course",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Course fetched successfully",
|
||||||
|
Data: course,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCoursesByCategory godoc
|
||||||
|
// @Summary List courses by category
|
||||||
|
// @Description Returns courses under a given category
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param category_id path int true "Category ID"
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.Course}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/course-categories/{category_id}/courses [get]
|
||||||
|
func (h *Handler) ListCoursesByCategory(c *fiber.Ctx) error {
|
||||||
|
catIDStr := c.Params("category_id")
|
||||||
|
catID, err := strconv.ParseInt(catIDStr, 10, 64)
|
||||||
|
if err != nil || catID <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid category ID",
|
||||||
|
Error: "ID must be a positive integer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
courses, err := h.courseMgmtSvc.ListCoursesByCategory(c.Context(), catID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch courses",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Courses fetched successfully",
|
||||||
|
Data: courses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListActiveCourses godoc
|
||||||
|
// @Summary List active courses
|
||||||
|
// @Description Returns all active courses
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.Course}
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/courses [get]
|
||||||
|
func (h *Handler) ListActiveCourses(c *fiber.Ctx) error {
|
||||||
|
courses, err := h.courseMgmtSvc.ListActiveCourses(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch courses",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Courses fetched successfully",
|
||||||
|
Data: courses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCourse godoc
|
||||||
|
// @Summary Update course
|
||||||
|
// @Description Updates a course
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Course ID"
|
||||||
|
// @Param course body domain.Course true "Course payload"
|
||||||
|
// @Success 200 {object} domain.Response{data=domain.Course}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/courses/{id} [put]
|
||||||
|
func (h *Handler) UpdateCourse(c *fiber.Ctx) error {
|
||||||
|
var req domain.Course
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid request body",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.courseMgmtSvc.UpdateCourse(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to update course",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Course updated successfully",
|
||||||
|
Data: updated,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeactivateCourse godoc
|
||||||
|
// @Summary Deactivate course
|
||||||
|
// @Description Deactivates a course
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Course ID"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/courses/{id}/deactivate [post]
|
||||||
|
func (h *Handler) DeactivateCourse(c *fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid course ID",
|
||||||
|
Error: "ID must be a positive integer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.courseMgmtSvc.DeactivateCourse(c.Context(), id); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to deactivate course",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Course deactivated",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Programs, Modules, Videos, Practices, Questions, Levels ---
|
||||||
|
|
||||||
|
// For brevity: implement representative handlers for creating and listing programs, modules, videos, practices, questions, and levels.
|
||||||
|
|
||||||
|
// CreateProgram godoc
|
||||||
|
// @Summary Create program
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param program body domain.Program true "Program payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.Program}
|
||||||
|
// @Router /api/v1/courses/{course_id}/programs [post]
|
||||||
|
func (h *Handler) CreateProgram(c *fiber.Ctx) error {
|
||||||
|
var req domain.Program
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
|
||||||
|
}
|
||||||
|
p, err := h.courseMgmtSvc.CreateProgram(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create program", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Program created", Data: p})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListProgramsByCourse godoc
|
||||||
|
// @Summary List programs by course
|
||||||
|
// @Tags courses
|
||||||
|
// @Param course_id path int true "Course ID"
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.Program}
|
||||||
|
// @Router /api/v1/courses/{course_id}/programs [get]
|
||||||
|
func (h *Handler) ListProgramsByCourse(c *fiber.Ctx) error {
|
||||||
|
courseIDStr := c.Params("course_id")
|
||||||
|
courseID, err := strconv.ParseInt(courseIDStr, 10, 64)
|
||||||
|
if err != nil || courseID <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid course ID", Error: "ID must be a positive integer"})
|
||||||
|
}
|
||||||
|
items, err := h.courseMgmtSvc.ListProgramsByCourse(c.Context(), courseID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to fetch programs", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{Message: "Programs fetched", Data: items})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateModule godoc
|
||||||
|
// @Summary Create module
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param module body domain.Module true "Module payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.Module}
|
||||||
|
// @Router /api/v1/modules [post]
|
||||||
|
func (h *Handler) CreateModule(c *fiber.Ctx) error {
|
||||||
|
var req domain.Module
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
|
||||||
|
}
|
||||||
|
m, err := h.courseMgmtSvc.CreateModule(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create module", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Module created", Data: m})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListModulesByLevel godoc
|
||||||
|
// @Summary List modules by level
|
||||||
|
// @Tags courses
|
||||||
|
// @Param level_id path int true "Level ID"
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.Module}
|
||||||
|
// @Router /api/v1/levels/{level_id}/modules [get]
|
||||||
|
func (h *Handler) ListModulesByLevel(c *fiber.Ctx) error {
|
||||||
|
lvlStr := c.Params("level_id")
|
||||||
|
lvlID, err := strconv.ParseInt(lvlStr, 10, 64)
|
||||||
|
if err != nil || lvlID <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid level ID", Error: "ID must be a positive integer"})
|
||||||
|
}
|
||||||
|
items, err := h.courseMgmtSvc.ListModulesByLevel(c.Context(), lvlID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to fetch modules", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{Message: "Modules fetched", Data: items})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateModuleVideo godoc
|
||||||
|
// @Summary Create module video
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param video body domain.ModuleVideo true "Module video payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.ModuleVideo}
|
||||||
|
// @Router /api/v1/module-videos [post]
|
||||||
|
func (h *Handler) CreateModuleVideo(c *fiber.Ctx) error {
|
||||||
|
var req domain.ModuleVideo
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
|
||||||
|
}
|
||||||
|
v, err := h.courseMgmtSvc.CreateModuleVideo(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create module video", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Module video created", Data: v})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePractice godoc
|
||||||
|
// @Summary Create practice
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param practice body domain.Practice true "Practice payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.Practice}
|
||||||
|
// @Router /api/v1/practices [post]
|
||||||
|
func (h *Handler) CreatePractice(c *fiber.Ctx) error {
|
||||||
|
var req domain.Practice
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
|
||||||
|
}
|
||||||
|
p, err := h.courseMgmtSvc.CreatePractice(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create practice", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Practice created", Data: p})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePracticeQuestion godoc
|
||||||
|
// @Summary Create practice question
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param question body domain.PracticeQuestion true "Practice question payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.PracticeQuestion}
|
||||||
|
// @Router /api/v1/practice-questions [post]
|
||||||
|
func (h *Handler) CreatePracticeQuestion(c *fiber.Ctx) error {
|
||||||
|
var req domain.PracticeQuestion
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
|
||||||
|
}
|
||||||
|
q, err := h.courseMgmtSvc.CreatePracticeQuestion(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create practice question", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Practice question created", Data: q})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLevel godoc
|
||||||
|
// @Summary Create level
|
||||||
|
// @Tags courses
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param level body domain.Level true "Level payload"
|
||||||
|
// @Success 201 {object} domain.Response{data=domain.Level}
|
||||||
|
// @Router /api/v1/levels [post]
|
||||||
|
func (h *Handler) CreateLevel(c *fiber.Ctx) error {
|
||||||
|
var req domain.Level
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
|
||||||
|
}
|
||||||
|
l, err := h.courseMgmtSvc.CreateLevel(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create level", Error: err.Error()})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Level created", Data: l})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to surface not-implemented errors for optional handlers
|
||||||
|
func notImplemented(c *fiber.Ctx, name string) error {
|
||||||
|
return c.Status(fiber.StatusNotImplemented).JSON(domain.ErrorResponse{Message: name + " not implemented", Error: "not implemented"})
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"Yimaru-Backend/internal/config"
|
"Yimaru-Backend/internal/config"
|
||||||
"Yimaru-Backend/internal/services/arifpay"
|
"Yimaru-Backend/internal/services/arifpay"
|
||||||
"Yimaru-Backend/internal/services/assessment"
|
"Yimaru-Backend/internal/services/assessment"
|
||||||
|
course_management "Yimaru-Backend/internal/services/course_management"
|
||||||
"Yimaru-Backend/internal/services/authentication"
|
"Yimaru-Backend/internal/services/authentication"
|
||||||
notificationservice "Yimaru-Backend/internal/services/notification"
|
notificationservice "Yimaru-Backend/internal/services/notification"
|
||||||
"Yimaru-Backend/internal/services/recommendation"
|
"Yimaru-Backend/internal/services/recommendation"
|
||||||
|
|
@ -23,6 +24,7 @@ import (
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
assessmentSvc *assessment.Service
|
assessmentSvc *assessment.Service
|
||||||
|
courseMgmtSvc *course_management.Service
|
||||||
arifpaySvc *arifpay.ArifpayService
|
arifpaySvc *arifpay.ArifpayService
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
settingSvc *settings.Service
|
settingSvc *settings.Service
|
||||||
|
|
@ -39,6 +41,7 @@ type Handler struct {
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
assessmentSvc *assessment.Service,
|
assessmentSvc *assessment.Service,
|
||||||
|
courseMgmtSvc *course_management.Service,
|
||||||
arifpaySvc *arifpay.ArifpayService,
|
arifpaySvc *arifpay.ArifpayService,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
settingSvc *settings.Service,
|
settingSvc *settings.Service,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
func (a *App) initAppRoutes() {
|
func (a *App) initAppRoutes() {
|
||||||
h := handlers.New(
|
h := handlers.New(
|
||||||
a.assessmentSvc,
|
a.assessmentSvc,
|
||||||
|
a.courseSvc,
|
||||||
a.arifpaySvc,
|
a.arifpaySvc,
|
||||||
a.logger,
|
a.logger,
|
||||||
a.settingSvc,
|
a.settingSvc,
|
||||||
|
|
@ -85,6 +86,33 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Get("/assessment/questions", h.GetActiveAssessmentQuestions)
|
groupV1.Get("/assessment/questions", h.GetActiveAssessmentQuestions)
|
||||||
tenant.Post("/assessment/submit", a.authMiddleware, h.SubmitAssessment)
|
tenant.Post("/assessment/submit", a.authMiddleware, h.SubmitAssessment)
|
||||||
|
|
||||||
|
// Course Management Routes
|
||||||
|
groupV1.Post("/course-categories", h.CreateCourseCategory)
|
||||||
|
groupV1.Get("/course-categories", h.ListActiveCourseCategories)
|
||||||
|
groupV1.Get("/course-categories/:id", h.GetCourseCategoryByID)
|
||||||
|
groupV1.Put("/course-categories/:id", h.UpdateCourseCategory)
|
||||||
|
groupV1.Post("/course-categories/:id/deactivate", h.DeactivateCourseCategory)
|
||||||
|
|
||||||
|
groupV1.Post("/courses", h.CreateCourse)
|
||||||
|
groupV1.Get("/courses", h.ListActiveCourses)
|
||||||
|
groupV1.Get("/courses/:id", h.GetCourseByID)
|
||||||
|
groupV1.Put("/courses/:id", h.UpdateCourse)
|
||||||
|
groupV1.Post("/courses/:id/deactivate", h.DeactivateCourse)
|
||||||
|
groupV1.Get("/course-categories/:category_id/courses", h.ListCoursesByCategory)
|
||||||
|
|
||||||
|
groupV1.Post("/courses/:course_id/programs", h.CreateProgram)
|
||||||
|
groupV1.Get("/courses/:course_id/programs", h.ListProgramsByCourse)
|
||||||
|
|
||||||
|
groupV1.Post("/modules", h.CreateModule)
|
||||||
|
groupV1.Get("/levels/:level_id/modules", h.ListModulesByLevel)
|
||||||
|
|
||||||
|
groupV1.Post("/module-videos", h.CreateModuleVideo)
|
||||||
|
|
||||||
|
groupV1.Post("/practices", h.CreatePractice)
|
||||||
|
groupV1.Post("/practice-questions", h.CreatePracticeQuestion)
|
||||||
|
|
||||||
|
groupV1.Post("/levels", h.CreateLevel)
|
||||||
|
|
||||||
// Auth Routes
|
// Auth Routes
|
||||||
tenant.Post("/auth/customer-login", h.LoginUser)
|
tenant.Post("/auth/customer-login", h.LoginUser)
|
||||||
tenant.Post("/auth/admin-login", h.LoginAdmin)
|
tenant.Post("/auth/admin-login", h.LoginAdmin)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user