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/assessment"
|
||||
"Yimaru-Backend/internal/services/authentication"
|
||||
"Yimaru-Backend/internal/services/course_management"
|
||||
issuereporting "Yimaru-Backend/internal/services/issue_reporting"
|
||||
"Yimaru-Backend/internal/services/messenger"
|
||||
notificationservice "Yimaru-Backend/internal/services/notification"
|
||||
|
|
@ -331,6 +332,14 @@ func main() {
|
|||
cfg,
|
||||
)
|
||||
|
||||
// Course management service
|
||||
courseSvc := course_management.NewService(
|
||||
repository.NewUserStore(store),
|
||||
repository.NewCourseStore(store),
|
||||
notificationSvc,
|
||||
cfg,
|
||||
)
|
||||
|
||||
arifpaySvc := arifpay.NewArifpayService(cfg, *transactionSvc, &http.Client{
|
||||
Timeout: 30 * time.Second})
|
||||
|
||||
|
|
@ -342,6 +351,7 @@ func main() {
|
|||
// Initialize and start HTTP server
|
||||
app := httpserver.NewApp(
|
||||
assessmentSvc,
|
||||
courseSvc,
|
||||
arifpaySvc,
|
||||
issueReportingSvc,
|
||||
cfg.Port,
|
||||
|
|
|
|||
|
|
@ -12,38 +12,8 @@ VALUES
|
|||
('certificate_enabled', 'true'),
|
||||
('max_courses_per_instructor', '50')
|
||||
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 (
|
||||
id,
|
||||
first_name,
|
||||
|
|
@ -51,16 +21,27 @@ INSERT INTO users (
|
|||
user_name,
|
||||
email,
|
||||
phone_number,
|
||||
password,
|
||||
role,
|
||||
password,
|
||||
age,
|
||||
education_level,
|
||||
country,
|
||||
region,
|
||||
knowledge_level,
|
||||
nick_name,
|
||||
occupation,
|
||||
learning_goal,
|
||||
language_goal,
|
||||
language_challange,
|
||||
favoutite_topic,
|
||||
initial_assessment_completed,
|
||||
email_verified,
|
||||
phone_verified,
|
||||
suspended,
|
||||
organization_id,
|
||||
status,
|
||||
last_login,
|
||||
profile_completed,
|
||||
profile_picture_url,
|
||||
preferred_language,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
|
|
@ -72,16 +53,27 @@ VALUES
|
|||
'SarahC',
|
||||
'yaredyemane1@gmail.com',
|
||||
NULL,
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
'SUPER_ADMIN',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
35,
|
||||
'Masters',
|
||||
'USA',
|
||||
'California',
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
FALSE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
'ACTIVE',
|
||||
NULL,
|
||||
FALSE,
|
||||
NULL,
|
||||
'en',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
),
|
||||
|
|
@ -92,16 +84,27 @@ VALUES
|
|||
'InstructorT',
|
||||
'instructor@yimaru.com',
|
||||
'0988554466',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
'INSTRUCTOR',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
30,
|
||||
'Bachelors',
|
||||
'USA',
|
||||
'New York',
|
||||
TRUE,
|
||||
TRUE,
|
||||
NULL,
|
||||
NULL,
|
||||
'Instructor',
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
FALSE,
|
||||
1,
|
||||
TRUE,
|
||||
TRUE,
|
||||
'ACTIVE',
|
||||
NULL,
|
||||
FALSE,
|
||||
NULL,
|
||||
'en',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
),
|
||||
|
|
@ -112,16 +115,27 @@ VALUES
|
|||
'DemoS',
|
||||
'student@yimaru.com',
|
||||
NULL,
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
'STUDENT',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
22,
|
||||
'High School',
|
||||
'USA',
|
||||
'Texas',
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
FALSE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
'ACTIVE',
|
||||
NULL,
|
||||
FALSE,
|
||||
1,
|
||||
NULL,
|
||||
'en',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
|
|
@ -131,110 +145,44 @@ SET first_name = EXCLUDED.first_name,
|
|||
user_name = EXCLUDED.user_name,
|
||||
email = EXCLUDED.email,
|
||||
phone_number = EXCLUDED.phone_number,
|
||||
password = EXCLUDED.password,
|
||||
role = EXCLUDED.role,
|
||||
password = EXCLUDED.password,
|
||||
age = EXCLUDED.age,
|
||||
education_level = EXCLUDED.education_level,
|
||||
country = EXCLUDED.country,
|
||||
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,
|
||||
phone_verified = EXCLUDED.phone_verified,
|
||||
suspended = EXCLUDED.suspended,
|
||||
organization_id = EXCLUDED.organization_id,
|
||||
status = EXCLUDED.status,
|
||||
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;
|
||||
|
||||
-- ======================================================
|
||||
-- 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,
|
||||
course_id,
|
||||
title,
|
||||
position,
|
||||
name,
|
||||
is_active,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
1,
|
||||
1,
|
||||
'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
|
||||
)
|
||||
VALUES
|
||||
(1, 'Learning English', TRUE, CURRENT_TIMESTAMP),
|
||||
(2, 'Other Courses', TRUE, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ======================================================
|
||||
|
|
|
|||
|
|
@ -1,46 +1,8 @@
|
|||
-- =========================================
|
||||
-- Notifications
|
||||
-- =========================================
|
||||
DROP TABLE IF EXISTS global_settings;
|
||||
|
||||
-- =========================================
|
||||
-- Notifications
|
||||
-- =========================================
|
||||
DROP TABLE IF EXISTS notifications;
|
||||
|
||||
|
||||
-- =========================================
|
||||
-- Issue Reporting
|
||||
-- =========================================
|
||||
DROP TABLE IF EXISTS reported_issues;
|
||||
|
||||
-- =========================================
|
||||
-- Assessments
|
||||
-- =========================================
|
||||
DROP TABLE IF EXISTS assessment_submissions;
|
||||
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 otps;
|
||||
|
||||
-- =========================================
|
||||
-- Users
|
||||
-- =========================================
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
|
@ -95,76 +95,6 @@ CREATE TABLE otps (
|
|||
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 (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
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:
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
error:
|
||||
|
|
@ -34,6 +62,31 @@ definitions:
|
|||
message:
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
caller:
|
||||
|
|
@ -65,6 +118,52 @@ definitions:
|
|||
pagination:
|
||||
$ref: '#/definitions/domain.Pagination'
|
||||
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:
|
||||
enum:
|
||||
- email
|
||||
|
|
@ -84,6 +183,80 @@ definitions:
|
|||
total_pages:
|
||||
type: integer
|
||||
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:
|
||||
properties:
|
||||
age:
|
||||
|
|
@ -1198,6 +1371,471 @@ paths:
|
|||
summary: Refresh token
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
description: Fetches application logs from MongoDB with pagination, level filtering,
|
||||
|
|
@ -1240,6 +1878,110 @@ paths:
|
|||
summary: Retrieve application logs with filtering and pagination
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
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"
|
||||
)
|
||||
|
||||
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 {
|
||||
ID int64 `json:"id"`
|
||||
AttemptID int64 `json:"attempt_id"`
|
||||
|
|
@ -55,44 +45,21 @@ type AssessmentQuestionOption struct {
|
|||
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 {
|
||||
ID int64 `json:"id"`
|
||||
InstructorID int64 `json:"instructor_id"`
|
||||
Title string `json:"title"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Level pgtype.Text `json:"level"`
|
||||
Language pgtype.Text `json:"language"`
|
||||
IsPublished bool `json:"is_published"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
ID int64 `json:"id"`
|
||||
CategoryID int64 `json:"category_id"`
|
||||
Title string `json:"title"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type CourseModule struct {
|
||||
type CourseCategory struct {
|
||||
ID int64 `json:"id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Title string `json:"title"`
|
||||
Position int32 `json:"position"`
|
||||
Name string `json:"name"`
|
||||
IsActive bool `json:"is_active"`
|
||||
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 {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
|
|
@ -100,23 +67,41 @@ type GlobalSetting struct {
|
|||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Lesson struct {
|
||||
ID int64 `json:"id"`
|
||||
ModuleID int64 `json:"module_id"`
|
||||
Title string `json:"title"`
|
||||
ContentType string `json:"content_type"`
|
||||
ContentUrl pgtype.Text `json:"content_url"`
|
||||
DurationMinutes pgtype.Int4 `json:"duration_minutes"`
|
||||
Position int32 `json:"position"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
type Level struct {
|
||||
ID int64 `json:"id"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type LessonProgress struct {
|
||||
ID int64 `json:"id"`
|
||||
LessonID int64 `json:"lesson_id"`
|
||||
StudentID int64 `json:"student_id"`
|
||||
Completed bool `json:"completed"`
|
||||
CompletedAt pgtype.Timestamptz `json:"completed_at"`
|
||||
type Module struct {
|
||||
ID int64 `json:"id"`
|
||||
LevelID int64 `json:"level_id"`
|
||||
Title string `json:"title"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
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 {
|
||||
|
|
@ -146,6 +131,38 @@ type Otp struct {
|
|||
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 {
|
||||
ID int64 `json:"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/assessment"
|
||||
"Yimaru-Backend/internal/services/authentication"
|
||||
"Yimaru-Backend/internal/services/course_management"
|
||||
issuereporting "Yimaru-Backend/internal/services/issue_reporting"
|
||||
notificationservice "Yimaru-Backend/internal/services/notification"
|
||||
"Yimaru-Backend/internal/services/recommendation"
|
||||
|
|
@ -26,6 +27,7 @@ import (
|
|||
|
||||
type App struct {
|
||||
assessmentSvc *assessment.Service
|
||||
courseSvc *course_management.Service
|
||||
arifpaySvc *arifpay.ArifpayService
|
||||
issueReportingSvc *issuereporting.Service
|
||||
fiber *fiber.App
|
||||
|
|
@ -46,6 +48,7 @@ type App struct {
|
|||
|
||||
func NewApp(
|
||||
assessmentSvc *assessment.Service,
|
||||
courseSvc *course_management.Service,
|
||||
arifpaySvc *arifpay.ArifpayService,
|
||||
issueReportingSvc *issuereporting.Service,
|
||||
port int, validator *customvalidator.CustomValidator,
|
||||
|
|
@ -78,6 +81,7 @@ func NewApp(
|
|||
|
||||
s := &App{
|
||||
assessmentSvc: assessmentSvc,
|
||||
courseSvc: courseSvc,
|
||||
arifpaySvc: arifpaySvc,
|
||||
// issueReportingSvc: issueReportingSvc,
|
||||
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/services/arifpay"
|
||||
"Yimaru-Backend/internal/services/assessment"
|
||||
course_management "Yimaru-Backend/internal/services/course_management"
|
||||
"Yimaru-Backend/internal/services/authentication"
|
||||
notificationservice "Yimaru-Backend/internal/services/notification"
|
||||
"Yimaru-Backend/internal/services/recommendation"
|
||||
|
|
@ -23,6 +24,7 @@ import (
|
|||
|
||||
type Handler struct {
|
||||
assessmentSvc *assessment.Service
|
||||
courseMgmtSvc *course_management.Service
|
||||
arifpaySvc *arifpay.ArifpayService
|
||||
logger *slog.Logger
|
||||
settingSvc *settings.Service
|
||||
|
|
@ -39,6 +41,7 @@ type Handler struct {
|
|||
|
||||
func New(
|
||||
assessmentSvc *assessment.Service,
|
||||
courseMgmtSvc *course_management.Service,
|
||||
arifpaySvc *arifpay.ArifpayService,
|
||||
logger *slog.Logger,
|
||||
settingSvc *settings.Service,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
func (a *App) initAppRoutes() {
|
||||
h := handlers.New(
|
||||
a.assessmentSvc,
|
||||
a.courseSvc,
|
||||
a.arifpaySvc,
|
||||
a.logger,
|
||||
a.settingSvc,
|
||||
|
|
@ -85,6 +86,33 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Get("/assessment/questions", h.GetActiveAssessmentQuestions)
|
||||
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
|
||||
tenant.Post("/auth/customer-login", h.LoginUser)
|
||||
tenant.Post("/auth/admin-login", h.LoginAdmin)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user