removed all unnecessary data seed

This commit is contained in:
Yared Yemane 2026-04-10 03:32:07 -07:00
parent 7613eb583a
commit 894e18bcae
13 changed files with 49 additions and 945 deletions

View File

@ -136,190 +136,6 @@ VALUES
) )
ON CONFLICT (id) DO NOTHING; ON CONFLICT (id) DO NOTHING;
-- Ensure seeded admin has full panel permissions in legacy team_members.permissions JSON.
-- RBAC permissions are managed separately, but this keeps seed behavior consistent.
UPDATE team_members
SET permissions = '["*"]'::jsonb
WHERE id = 2 OR email = 'admin@yimaru.com';
-- ======================================================
-- Global Settings (LMS)
-- ======================================================
INSERT INTO global_settings (key, value)
VALUES
('platform_name', 'Yimaru LMS'),
('default_language', 'en'),
('allow_self_signup', 'true'),
('otp_expiry_minutes', '5'),
('certificate_enabled', 'true'),
('max_courses_per_instructor', '50')
ON CONFLICT (key) DO NOTHING;
-- ======================================================
-- ======================================================
-- Questions - Level A2 (EASY)
-- ======================================================
INSERT INTO questions (id, question_text, question_type, difficulty_level, points, status)
VALUES
(1, 'What would you say to greet someone before lunchtime?', 'MCQ', 'EASY', 1, 'PUBLISHED'),
(2, 'Which question is correct to ask about your routine?', 'MCQ', 'EASY', 1, 'PUBLISHED'),
(3, 'She ___ like pizza.', 'MCQ', 'EASY', 1, 'PUBLISHED'),
(4, 'I usually go to school and start class ____ eight o''clock.', 'MCQ', 'EASY', 1, 'PUBLISHED'),
(5, 'Someone says, "Here is the book you asked for." What is the best response?', 'MCQ', 'EASY', 1, 'PUBLISHED')
ON CONFLICT (id) DO NOTHING;
INSERT INTO question_options (question_id, option_text, option_order, is_correct)
VALUES
-- Q1
(1, 'Good morning.', 1, TRUE),
(1, 'How do you do?', 2, FALSE),
(1, 'Good afternoon.', 3, FALSE),
(1, 'Goodbye.', 4, FALSE),
-- Q2
(2, 'What time you wake up?', 1, FALSE),
(2, 'What time do you wake up?', 2, TRUE),
(2, 'What time are you wake up?', 3, FALSE),
(2, 'What time waking you?', 4, FALSE),
-- Q3
(3, 'do not', 1, FALSE),
(3, 'not', 2, FALSE),
(3, 'is not', 3, FALSE),
(3, 'does not', 4, TRUE),
-- Q4
(4, 'about', 1, FALSE),
(4, 'on', 2, FALSE),
(4, 'at', 3, TRUE),
(4, 'in', 4, FALSE),
-- Q5
(5, 'Never mind.', 1, FALSE),
(5, 'Really?', 2, FALSE),
(5, 'What a pity!', 3, FALSE),
(5, 'Thank you.', 4, TRUE);
-- ======================================================
-- Questions - Level B1 (MEDIUM)
-- ======================================================
INSERT INTO questions (id, question_text, question_type, difficulty_level, points, status)
VALUES
(6, 'How do you introduce your friend to another person?', 'MCQ', 'MEDIUM', 1, 'PUBLISHED'),
(7, 'How would you ask for the price of an item in a shop?', 'MCQ', 'MEDIUM', 1, 'PUBLISHED'),
(8, 'Which sentence correctly gives simple directions?', 'MCQ', 'MEDIUM', 1, 'PUBLISHED'),
(9, 'The watch shows 10:50, but the real time is 10:45. What can you say?', 'MCQ', 'MEDIUM', 1, 'PUBLISHED'),
(10, 'Which instruction is correct when giving directions?', 'MCQ', 'MEDIUM', 1, 'PUBLISHED')
ON CONFLICT (id) DO NOTHING;
INSERT INTO question_options (question_id, option_text, option_order, is_correct)
VALUES
-- Q6
(6, 'Hello, my name is Samson.', 1, FALSE),
(6, 'Good morning. Nice to meet you.', 2, FALSE),
(6, 'Let me introduce myself to my friend.', 3, FALSE),
(6, 'This is my friend, Samson.', 4, TRUE),
-- Q7
(7, 'How many are these?', 1, FALSE),
(7, 'What is this?', 2, FALSE),
(7, 'How much is this?', 3, TRUE),
(7, 'Where is the nearest shop?', 4, FALSE),
-- Q8
(8, 'Thank you very much for asking.', 1, FALSE),
(8, 'Turn left and walk two blocks.', 2, TRUE),
(8, 'Why don''t you eat out.', 3, FALSE),
(8, 'Take the bus to the park.', 4, FALSE),
-- Q9
(9, 'My watch is slow.', 1, TRUE),
(9, 'My watch is late.', 2, FALSE),
(9, 'My watch is fast.', 3, FALSE),
(9, 'My watch is early.', 4, FALSE),
-- Q10
(10, 'Turn left.', 1, TRUE),
(10, 'Turn on left.', 2, FALSE),
(10, 'Turn left side.', 3, FALSE),
(10, 'Turn to straight.', 4, FALSE);
-- ======================================================
-- Questions - Level B2 (HARD)
-- ======================================================
INSERT INTO questions (id, question_text, question_type, difficulty_level, points, status)
VALUES
(11, 'What is the most polite way to ask to speak to someone on the phone?', 'MCQ', 'HARD', 1, 'PUBLISHED'),
(12, 'How do you correctly state the age of a person who is 30 years old?', 'MCQ', 'HARD', 1, 'PUBLISHED'),
(13, 'When asking for help with a new Yimaru App feature, which option is most appropriate?', 'MCQ', 'HARD', 1, 'PUBLISHED'),
(14, 'Which word has the unvoiced "th" sound?', 'MCQ', 'HARD', 1, 'PUBLISHED'),
(15, 'Which sentence sounds like a warning, not friendly advice?', 'MCQ', 'HARD', 1, 'PUBLISHED'),
(16, 'What does this sentence mean? "I will definitely be there on time."', 'MCQ', 'HARD', 1, 'PUBLISHED')
ON CONFLICT (id) DO NOTHING;
INSERT INTO question_options (question_id, option_text, option_order, is_correct)
VALUES
-- Q11
(11, 'May I speak to Mr. Tesfaye, please?', 1, TRUE),
(11, 'Can I talk to Mr. Tesfaye?', 2, FALSE),
(11, 'Is Mr. Tesfaye there?', 3, FALSE),
(11, 'I want to talk to Mr. Tesfaye.', 4, FALSE),
-- Q12
(12, 'He is thirty years.', 1, FALSE),
(12, 'He has thirty years.', 2, FALSE),
(12, 'He has thirty years old.', 3, FALSE),
(12, 'He is thirty.', 4, TRUE),
-- Q13
(13, 'Are you familiar with how this feature works?', 1, FALSE),
(13, 'Could you walk me through how this feature works?', 2, TRUE),
(13, 'I believe I understand how this feature works.', 3, FALSE),
(13, 'I''ve tried similar features before.', 4, FALSE),
-- Q14
(14, 'That', 1, FALSE),
(14, 'They', 2, FALSE),
(14, 'These', 3, FALSE),
(14, 'Three', 4, TRUE),
-- Q15
(15, 'You might want to plan your time better.', 1, FALSE),
(15, 'If I were you, I''d start earlier.', 2, FALSE),
(15, 'You''d better meet the deadline this time.', 3, TRUE),
(15, 'Why don''t you try using a planner?', 4, FALSE),
-- Q16
(16, 'The speaker is unsure about arriving.', 1, FALSE),
(16, 'The speaker is promising to arrive on time.', 2, TRUE),
(16, 'The speaker might arrive late.', 3, FALSE),
(16, 'The speaker has already arrived.', 4, FALSE);
-- ======================================================
-- Initial Assessment Question Set
-- ======================================================
INSERT INTO question_sets (id, title, description, set_type, owner_type, status)
VALUES
(1, 'Initial Assessment', 'Default initial assessment for new users', 'INITIAL_ASSESSMENT', 'STANDALONE', 'PUBLISHED')
ON CONFLICT (id) DO NOTHING;
INSERT INTO question_set_items (set_id, question_id, display_order)
VALUES
(1, 1, 1), (1, 2, 2), (1, 3, 3), (1, 4, 4), (1, 5, 5),
(1, 6, 6), (1, 7, 7), (1, 8, 8), (1, 9, 9), (1, 10, 10),
(1, 11, 11), (1, 12, 12), (1, 13, 13), (1, 14, 14), (1, 15, 15), (1, 16, 16)
ON CONFLICT (set_id, question_id) DO NOTHING;
-- ======================================================
-- Course Management seed data removed intentionally.
-- Course/category/sub-course/video/practice/question-set fixtures
-- are no longer seeded from this baseline script.
-- ======================================================
-- ====================================================== -- ======================================================
-- Team Members / Admin Panel Users (login via /api/v1/team/login) -- Team Members / Admin Panel Users (login via /api/v1/team/login)
-- Credentials: email + password@123 -- Credentials: email + password@123
@ -471,3 +287,8 @@ VALUES
CURRENT_TIMESTAMP CURRENT_TIMESTAMP
) )
ON CONFLICT (id) DO NOTHING; ON CONFLICT (id) DO NOTHING;
-- Legacy team_members row may pre-exist; align admin permissions with seed expectations.
UPDATE team_members
SET permissions = '["*"]'::jsonb
WHERE id = 2 OR email = 'admin@yimaru.com';

View File

@ -1,108 +1,25 @@
-- ====================================================== -- Reset sequences for tables touched by login-only seed (PostgreSQL)
-- Reset sequences for LMS tables (PostgreSQL)
-- ======================================================
-- users.id (BIGSERIAL)
SELECT setval( SELECT setval(
pg_get_serial_sequence('users', 'id'), pg_get_serial_sequence('users', 'id'),
COALESCE((SELECT MAX(id) FROM users), 1), COALESCE((SELECT MAX(id) FROM users), 1),
true true
); );
-- questions.id (BIGSERIAL)
SELECT setval( SELECT setval(
pg_get_serial_sequence('questions', 'id'), pg_get_serial_sequence('team_members', 'id'),
COALESCE((SELECT MAX(id) FROM questions), 1), COALESCE((SELECT MAX(id) FROM team_members), 1),
true true
); );
-- question_options.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('question_options', 'id'),
COALESCE((SELECT MAX(id) FROM question_options), 1),
true
);
-- question_short_answers.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('question_short_answers', 'id'),
COALESCE((SELECT MAX(id) FROM question_short_answers), 1),
true
);
-- question_sets.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('question_sets', 'id'),
COALESCE((SELECT MAX(id) FROM question_sets), 1),
true
);
-- question_set_items.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('question_set_items', 'id'),
COALESCE((SELECT MAX(id) FROM question_set_items), 1),
true
);
-- refresh_tokens.id (BIGSERIAL)
SELECT setval( SELECT setval(
pg_get_serial_sequence('refresh_tokens', 'id'), pg_get_serial_sequence('refresh_tokens', 'id'),
COALESCE((SELECT MAX(id) FROM refresh_tokens), 1), COALESCE((SELECT MAX(id) FROM refresh_tokens), 1),
true true
); );
-- otps.id (BIGSERIAL)
SELECT setval( SELECT setval(
pg_get_serial_sequence('otps', 'id'), pg_get_serial_sequence('otps', 'id'),
COALESCE((SELECT MAX(id) FROM otps), 1), COALESCE((SELECT MAX(id) FROM otps), 1),
true true
); );
-- notifications.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('notifications', 'id'),
COALESCE((SELECT MAX(id) FROM notifications), 1),
true
);
-- reported_issues.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('reported_issues', 'id'),
COALESCE((SELECT MAX(id) FROM reported_issues), 1),
true
);
-- course_categories.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('course_categories', 'id'),
COALESCE((SELECT MAX(id) FROM course_categories), 1),
true
);
-- courses.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('courses', 'id'),
COALESCE((SELECT MAX(id) FROM courses), 1),
true
);
-- sub_courses.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('sub_courses', 'id'),
COALESCE((SELECT MAX(id) FROM sub_courses), 1),
true
);
-- sub_course_videos.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('sub_course_videos', 'id'),
COALESCE((SELECT MAX(id) FROM sub_course_videos), 1),
true
);
-- question_set_personas.id (BIGSERIAL)
SELECT setval(
pg_get_serial_sequence('question_set_personas', 'id'),
COALESCE((SELECT MAX(id) FROM question_set_personas), 1),
true
);

View File

@ -1,31 +1 @@
INSERT INTO activity_logs (actor_id, actor_role, action, resource_type, resource_id, message, metadata, ip_address, user_agent, created_at) VALUES -- Intentionally empty: no demo activity log seed (login-only seed in 001).
(1, 'SUPER_ADMIN', 'CATEGORY_CREATED', 'CATEGORY', 1, 'Created course category: Mathematics', '{"name": "Mathematics"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '30 days'),
(1, 'SUPER_ADMIN', 'CATEGORY_CREATED', 'CATEGORY', 2, 'Created course category: Science', '{"name": "Science"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '29 days'),
(1, 'SUPER_ADMIN', 'CATEGORY_CREATED', 'CATEGORY', 3, 'Created course category: Language Arts', '{"name": "Language Arts"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '28 days'),
(1, 'SUPER_ADMIN', 'COURSE_CREATED', 'COURSE', 1, 'Created course: Algebra Fundamentals', '{"title": "Algebra Fundamentals", "category_id": 1}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '27 days'),
(1, 'SUPER_ADMIN', 'COURSE_CREATED', 'COURSE', 2, 'Created course: Biology 101', '{"title": "Biology 101", "category_id": 2}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '26 days'),
(2, 'ADMIN', 'COURSE_CREATED', 'COURSE', 3, 'Created course: English Grammar', '{"title": "English Grammar", "category_id": 3}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '25 days'),
(1, 'SUPER_ADMIN', 'SUB_COURSE_CREATED', 'SUB_COURSE', 1, 'Created sub-course: Linear Equations', '{"title": "Linear Equations", "course_id": 1, "level": "BEGINNER"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '24 days'),
(1, 'SUPER_ADMIN', 'SUB_COURSE_CREATED', 'SUB_COURSE', 2, 'Created sub-course: Quadratic Equations', '{"title": "Quadratic Equations", "course_id": 1, "level": "INTERMEDIATE"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '23 days'),
(2, 'ADMIN', 'SUB_COURSE_CREATED', 'SUB_COURSE', 3, 'Created sub-course: Cell Biology', '{"title": "Cell Biology", "course_id": 2, "level": "BEGINNER"}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '22 days'),
(1, 'SUPER_ADMIN', 'VIDEO_CREATED', 'VIDEO', 1, 'Created video: Introduction to Algebra', '{"title": "Introduction to Algebra", "sub_course_id": 1}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '21 days'),
(1, 'SUPER_ADMIN', 'VIDEO_UPLOADED', 'VIDEO', 1, 'Uploaded video to Vimeo: Introduction to Algebra', '{"title": "Introduction to Algebra", "vimeo_id": "987654321", "file_size": 52428800}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '21 days'),
(1, 'SUPER_ADMIN', 'VIDEO_PUBLISHED', 'VIDEO', 1, 'Published video: Introduction to Algebra', '{"title": "Introduction to Algebra"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '20 days'),
(2, 'ADMIN', 'VIDEO_CREATED', 'VIDEO', 2, 'Created video: Solving for X', '{"title": "Solving for X", "sub_course_id": 1}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '19 days'),
(2, 'ADMIN', 'VIDEO_UPLOADED', 'VIDEO', 2, 'Uploaded video to Vimeo: Solving for X', '{"title": "Solving for X", "vimeo_id": "987654322", "file_size": 41943040}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '19 days'),
(1, 'SUPER_ADMIN', 'COURSE_UPDATED', 'COURSE', 1, 'Updated course: Algebra Fundamentals', '{"title": "Algebra Fundamentals", "changed_fields": ["description", "thumbnail"]}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '18 days'),
(1, 'SUPER_ADMIN', 'CATEGORY_UPDATED', 'CATEGORY', 1, 'Updated course category: Mathematics & Statistics', '{"name": "Mathematics & Statistics"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '17 days'),
(2, 'ADMIN', 'VIDEO_CREATED', 'VIDEO', 3, 'Created video: Cell Structure Overview', '{"title": "Cell Structure Overview", "sub_course_id": 3}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '15 days'),
(2, 'ADMIN', 'VIDEO_UPLOADED', 'VIDEO', 3, 'Uploaded video to Vimeo: Cell Structure Overview', '{"title": "Cell Structure Overview", "vimeo_id": "987654323", "file_size": 73400320}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '15 days'),
(2, 'ADMIN', 'VIDEO_PUBLISHED', 'VIDEO', 2, 'Published video: Solving for X', '{"title": "Solving for X"}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '14 days'),
(1, 'SUPER_ADMIN', 'SUB_COURSE_UPDATED', 'SUB_COURSE', 2, 'Updated sub-course: Quadratic Equations', '{"title": "Quadratic Equations", "changed_fields": ["description"]}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '12 days'),
(2, 'ADMIN', 'VIDEO_UPDATED', 'VIDEO', 3, 'Updated video: Cell Structure Overview', '{"title": "Cell Structure Overview", "changed_fields": ["thumbnail", "resolution"]}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '10 days'),
(2, 'ADMIN', 'VIDEO_PUBLISHED', 'VIDEO', 3, 'Published video: Cell Structure Overview', '{"title": "Cell Structure Overview"}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '9 days'),
(1, 'SUPER_ADMIN', 'VIDEO_ARCHIVED', 'VIDEO', 4, 'Archived video ID: 4', '{"id": 4}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '7 days'),
(1, 'SUPER_ADMIN', 'SETTINGS_UPDATED', 'SETTINGS', NULL, 'Updated global settings', '{"keys": ["site_name", "maintenance_mode"]}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '5 days'),
(1, 'SUPER_ADMIN', 'TEAM_MEMBER_CREATED', 'TEAM_MEMBER', 3, 'Created team member: John Doe', '{"name": "John Doe", "role": "instructor"}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '4 days'),
(1, 'SUPER_ADMIN', 'COURSE_CREATED', 'COURSE', 4, 'Created course: Advanced Physics', '{"title": "Advanced Physics", "category_id": 2}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '3 days'),
(2, 'ADMIN', 'CATEGORY_DELETED', 'CATEGORY', 5, 'Deleted category ID: 5', '{"id": 5}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '2 days'),
(1, 'SUPER_ADMIN', 'SUB_COURSE_DELETED', 'SUB_COURSE', 6, 'Deleted sub-course ID: 6', '{"id": 6}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '1 day'),
(2, 'ADMIN', 'VIDEO_DELETED', 'VIDEO', 5, 'Deleted video ID: 5', '{"id": 5}', '10.0.0.55', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', now() - interval '6 hours'),
(1, 'SUPER_ADMIN', 'TEAM_MEMBER_UPDATED', 'TEAM_MEMBER', 3, 'Updated team member: John Doe', '{"name": "John Doe", "changed_fields": ["role"]}', '192.168.1.10', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', now() - interval '2 hours');

View File

@ -1,14 +1 @@
INSERT INTO reported_issues (user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at) VALUES -- Intentionally empty: no demo issue-report seed (login-only seed in 001).
(10, 'USER', 'Video not loading on mobile', 'When I try to play the Algebra Fundamentals introduction video on my phone, it shows a blank screen with a spinner that never stops.', 'video', 'pending', '{"course": "Algebra Fundamentals", "device": "iPhone 14", "browser": "Safari 17"}', now() - interval '14 days', now() - interval '14 days'),
(10, 'USER', 'Payment confirmation not received', 'I subscribed to the premium plan yesterday and the money was deducted from my account, but I have not received any confirmation email or SMS.', 'payment', 'in_progress', '{"plan": "Premium", "amount": 500, "payment_method": "telebirr"}', now() - interval '10 days', now() - interval '8 days'),
(10, 'USER', 'Cannot change profile picture', 'I am trying to upload a new profile picture but the upload button does not respond when I click it.', 'account', 'resolved', '{"browser": "Chrome 120", "file_type": "jpg", "file_size_kb": 2048}', now() - interval '20 days', now() - interval '15 days'),
(10, 'USER', 'Add dark mode support', 'It would be great if the platform had a dark mode option. Studying at night with the bright white background is hard on the eyes.', 'feature_request', 'pending', '{"platform": "web"}', now() - interval '7 days', now() - interval '7 days'),
(10, 'USER', 'Quiz results not saving', 'I completed the Biology 101 quiz but when I go back to check my results, it shows as incomplete.', 'bug', 'in_progress', '{"course": "Biology 101", "quiz_id": 5, "attempts": 3}', now() - interval '5 days', now() - interval '3 days'),
(12, 'SUPPORT', 'Course content displays incorrectly on tablets', 'Multiple users have reported that course text overlaps with images on tablet devices in landscape mode.', 'content', 'pending', '{"affected_devices": ["iPad Air", "Samsung Galaxy Tab S9"], "orientation": "landscape"}', now() - interval '12 days', now() - interval '12 days'),
(12, 'SUPPORT', 'Login fails after password reset', 'After resetting my password through the forgot password flow, the new password is not accepted for login.', 'login', 'resolved', '{"browser": "Firefox 121", "reset_method": "email"}', now() - interval '25 days', now() - interval '18 days'),
(12, 'SUPPORT', 'Slow page load times', 'The course listing page takes over 10 seconds to load, especially when filtering by category.', 'performance', 'in_progress', '{"page": "/courses", "avg_load_time_ms": 12500, "filter": "category=Science"}', now() - interval '9 days', now() - interval '6 days'),
(10, 'USER', 'Subscription auto-renewal not working', 'My monthly subscription expired even though I had auto-renewal enabled. I had to manually resubscribe.', 'subscription', 'rejected', '{"plan": "Monthly Basic", "expected_renewal": "2026-01-15"}', now() - interval '30 days', now() - interval '22 days'),
(12, 'SUPPORT', 'Screen reader cannot read course navigation', 'The course sidebar navigation is not accessible with screen readers. ARIA labels are missing on several interactive elements.', 'accessibility', 'pending', '{"screen_reader": "NVDA", "browser": "Chrome 120", "affected_elements": ["sidebar nav", "progress bar", "video controls"]}', now() - interval '4 days', now() - interval '4 days'),
(10, 'USER', 'Certificate download gives 404 error', 'After completing the English Grammar course, clicking the download certificate button returns a page not found error.', 'course', 'pending', '{"course": "English Grammar", "completion_date": "2026-01-28"}', now() - interval '2 days', now() - interval '2 days'),
(10, 'USER', 'Cannot access course after subscription renewal', 'I renewed my subscription but I still cannot access premium courses. It says my subscription is inactive.', 'subscription', 'in_progress', '{"plan": "Premium Annual", "renewal_date": "2026-02-01"}', now() - interval '1 day', now() - interval '12 hours')
ON CONFLICT DO NOTHING;

View File

@ -1,40 +1 @@
INSERT INTO notifications ( -- Intentionally empty: no demo notification seed (login-only seed in 001).
id, user_id, receiver_type, type, level, channel, title, message, payload, is_read, created_at
) VALUES
-- Learner notifications (receiver_type=user, user_id=10)
(1001, 10, 'user', 'course_created', 'info', 'in_app', 'New Course Available', 'A new course "Algebra Fundamentals" has been added. Check it out!', '{"course_title": "Algebra Fundamentals", "category": "Mathematics"}', false, now() - interval '30 days'),
(1002, 10, 'user', 'course_created', 'info', 'in_app', 'New Course Available', 'A new course "English Grammar 101" has been added. Check it out!', '{"course_title": "English Grammar 101", "category": "Language"}', false, now() - interval '25 days'),
(1003, 10, 'user', 'sub_course_created', 'info', 'in_app', 'New Content Available', 'A new sub-course "Linear Equations" has been added.', '{"sub_course_title": "Linear Equations", "course": "Algebra Fundamentals"}', false, now() - interval '24 days'),
(1004, 10, 'user', 'video_added', 'info', 'in_app', 'New Video Available', 'A new video "Introduction to Variables" has been added.', '{"video_title": "Introduction to Variables", "sub_course": "Linear Equations"}', false, now() - interval '23 days'),
(1005, 10, 'user', 'payment_verified', 'success', 'in_app', 'Payment Successful', 'Your payment has been verified successfully. Your subscription is now active.', '{"plan": "Premium Monthly", "amount": 500}', true, now() - interval '20 days'),
(1006, 10, 'user', 'subscription_activated', 'success', 'in_app', 'Subscription Activated', 'Your Premium Monthly subscription is now active until March 20, 2026.', '{"plan": "Premium Monthly", "expires": "2026-03-20"}', true, now() - interval '20 days'),
(1007, 10, 'user', 'knowledge_level_update', 'info', 'in_app', 'Knowledge Level Updated', 'Your knowledge level has been updated to: Intermediate', '{"previous_level": "Beginner", "new_level": "Intermediate"}', false, now() - interval '15 days'),
(1008, 10, 'user', 'issue_status_updated', 'info', 'in_app', 'Issue Status Updated', 'Your issue "Video not loading on mobile" has been updated to: in_progress', '{"issue_id": 1, "subject": "Video not loading on mobile", "status": "in_progress"}', true, now() - interval '12 days'),
(1009, 10, 'user', 'issue_status_updated', 'success', 'in_app', 'Issue Status Updated', 'Your issue "Cannot change profile picture" has been updated to: resolved', '{"issue_id": 3, "subject": "Cannot change profile picture", "status": "resolved"}', true, now() - interval '10 days'),
(1010, 10, 'user', 'course_enrolled', 'success', 'in_app', 'Course Enrolled', 'You have been enrolled in "Biology 101".', '{"course_title": "Biology 101"}', false, now() - interval '8 days'),
(1011, 10, 'user', 'assessment_assigned', 'info', 'in_app', 'New Assessment Available', 'A new assessment is available for "Algebra Fundamentals".', '{"course": "Algebra Fundamentals", "assessment_type": "quiz"}', false, now() - interval '5 days'),
(1012, 10, 'user', 'announcement', 'info', 'in_app', 'Platform Maintenance', 'Scheduled maintenance on Feb 15, 2026 from 2:00 AM - 4:00 AM EAT.', '{"scheduled_at": "2026-02-15T02:00:00+03:00", "duration_hours": 2}', false, now() - interval '2 days'),
(1013, 10, 'user', 'video_added', 'info', 'in_app', 'New Video Available', 'A new video "Solving Quadratic Equations" has been added.', '{"video_title": "Solving Quadratic Equations", "sub_course": "Quadratics"}', false, now() - interval '1 day'),
-- Team member notifications (receiver_type=team_member, user_id references team_members.id)
(1014, 2, 'team_member', 'issue_created', 'info', 'in_app', 'New Issue Reported', 'A new issue "Video not loading on mobile" has been reported.', '{"issue_id": 1, "subject": "Video not loading on mobile", "reporter_id": 10}', false, now() - interval '14 days'),
(1015, 2, 'team_member', 'issue_created', 'info', 'in_app', 'New Issue Reported', 'A new issue "Payment confirmation not received" has been reported.', '{"issue_id": 2, "subject": "Payment confirmation not received", "reporter_id": 10}', false, now() - interval '10 days'),
(1016, 2, 'team_member', 'issue_created', 'info', 'in_app', 'New Issue Reported', 'A new issue "Quiz results not saving" has been reported.', '{"issue_id": 5, "subject": "Quiz results not saving", "reporter_id": 10}', false, now() - interval '5 days'),
(1017, 2, 'team_member', 'user_deleted', 'warning', 'in_app', 'User Deleted', 'User ID 99 has been deleted.', '{"deleted_user_id": 99, "deleted_by": 2}', true, now() - interval '18 days'),
(1018, 2, 'team_member', 'admin_created', 'info', 'in_app', 'New Admin Created', 'A new admin account has been created for admin@yimaru.com.', '{"admin_email": "admin@yimaru.com"}', true, now() - interval '28 days'),
(1019, 2, 'team_member', 'team_member_created','info', 'in_app', 'New Team Member', 'A new team member has been added.', '{"member_email": "support@yimaru.com", "role": "support"}', true, now() - interval '26 days'),
(1020, 2, 'team_member', 'system_alert', 'warning', 'in_app', 'High Error Rate Detected', 'The notification delivery failure rate exceeded 5% in the last hour.', '{"failure_rate": 5.2, "window": "1h"}', false, now() - interval '3 days'),
(1021, 3, 'team_member', 'announcement', 'info', 'in_app', 'Weekly Registration Report','15 new students registered this week.', '{"count": 15, "period": "weekly"}', false, now() - interval '1 day')
ON CONFLICT (id) DO NOTHING;
-- Scheduled notifications seeds (created_by references users.id)
INSERT INTO scheduled_notifications (
id, channel, title, message, html, scheduled_at, status, target_user_ids, target_role, target_raw,
attempt_count, last_error, processing_started_at, sent_at, cancelled_at, created_by, created_at, updated_at
) VALUES
(2001, 'push', 'Reminder: Continue Your Lesson', 'Pick up where you left off and continue learning today.', NULL, now() + interval '6 hours', 'pending', ARRAY[10,11], NULL, NULL, 0, NULL, NULL, NULL, NULL, 10, now() - interval '1 day', now() - interval '1 day'),
(2002, 'email', 'Weekly Progress Summary', 'Your weekly course progress summary is ready.', '<p>Your weekly course progress summary is ready.</p>', now() + interval '1 day', 'pending', NULL, 'STUDENT', NULL, 0, NULL, NULL, NULL, NULL, 10, now() - interval '1 day', now() - interval '1 day'),
(2003, 'sms', 'Platform Maintenance', 'Scheduled maintenance tonight from 02:00 to 04:00 EAT.', NULL, now() - interval '2 days', 'sent', ARRAY[10,12], NULL, NULL, 1, NULL, now() - interval '2 days' - interval '5 minutes', now() - interval '2 days', NULL, 10, now() - interval '3 days', now() - interval '2 days'),
(2004, 'email', 'Payment Service Alert', 'Some users may experience delayed payment confirmation.', '<p>Some users may experience delayed payment confirmation.</p>', now() - interval '1 day', 'failed', NULL, 'SUPPORT', NULL, 3, 'SMTP temporary outage', now() - interval '1 day' - interval '15 minutes', NULL, NULL, 10, now() - interval '2 days', now() - interval '1 day'),
(2005, 'push', 'Obsolete Campaign', 'This campaign was cancelled by admin.', NULL, now() + interval '2 days', 'cancelled', NULL, NULL, '{"segment":"inactive_users"}'::jsonb, 0, NULL, NULL, NULL, now() - interval '12 hours', 10, now() - interval '1 day', now() - interval '12 hours')
ON CONFLICT (id) DO NOTHING;

View File

@ -1,469 +1,2 @@
-- ====================================================== -- Intentionally empty: course hierarchy is not seeded from SQL.
-- Complete Course Management Seed Data -- Use admin/API or migrations to create content.
-- Covers: categories, courses, sub-courses, videos,
-- question sets, questions, options, prerequisites,
-- and user progress for admin panel integration
-- ======================================================
-- ======================================================
-- Course Categories (supplement existing 3 categories)
-- Existing: 1=Programming, 2=Data Science, 3=Web Development
-- ======================================================
INSERT INTO course_categories (id, name, is_active, created_at) VALUES
(4, 'Mobile Development', TRUE, CURRENT_TIMESTAMP),
(5, 'DevOps & Cloud', TRUE, CURRENT_TIMESTAMP),
(6, 'Cybersecurity', FALSE, CURRENT_TIMESTAMP)
ON CONFLICT (id) DO NOTHING;
-- ======================================================
-- Courses (supplement existing 7 courses)
-- Existing: 1-7 in categories 1-3
-- ======================================================
INSERT INTO courses (id, category_id, title, description, thumbnail, intro_video_url, is_active) VALUES
(8, 4, 'Flutter App Development', 'Build cross-platform mobile apps with Flutter and Dart', 'https://example.com/thumbnails/flutter.jpg', 'https://example.com/intro/flutter.mp4', TRUE),
(9, 4, 'React Native Essentials', 'Create native mobile apps using React Native', 'https://example.com/thumbnails/react-native.jpg', NULL, TRUE),
(10, 5, 'Docker & Kubernetes', 'Container orchestration and deployment strategies', 'https://example.com/thumbnails/docker-k8s.jpg', 'https://example.com/intro/docker.mp4', TRUE),
(11, 5, 'CI/CD Pipeline Mastery', 'Automate your build, test, and deployment workflows', 'https://example.com/thumbnails/cicd.jpg', NULL, FALSE),
(12, 6, 'Ethical Hacking Fundamentals', 'Learn penetration testing and security analysis', 'https://example.com/thumbnails/ethical-hacking.jpg', NULL, FALSE)
ON CONFLICT (id) DO NOTHING;
-- ======================================================
-- Sub-courses (supplement existing 17 sub-courses: IDs 1-17)
-- ======================================================
INSERT INTO sub_courses (id, course_id, title, description, thumbnail, display_order, level, sub_level, is_active) VALUES
-- Flutter sub-courses (course 8) — IDs 18-21
(18, 8, 'Dart Language Basics', 'Learn Dart programming language fundamentals', NULL, 1, 'BEGINNER', 'A1', TRUE),
(19, 8, 'Flutter UI Widgets', 'Build beautiful UIs with Flutter widgets', NULL, 2, 'BEGINNER', 'A2', TRUE),
(20, 8, 'State Management', 'Manage app state with Provider and Riverpod', NULL, 3, 'INTERMEDIATE', 'B1', TRUE),
(21, 8, 'Flutter Networking & APIs', 'HTTP requests, REST APIs, and data persistence', NULL, 4, 'ADVANCED', 'C1', TRUE),
-- React Native sub-courses (course 9) — IDs 22-24
(22, 9, 'React Native Setup', 'Environment setup and first app', NULL, 1, 'BEGINNER', 'A1', TRUE),
(23, 9, 'Navigation & Routing', 'React Navigation and screen management', NULL, 2, 'INTERMEDIATE', 'B1', TRUE),
(24, 9, 'Native Modules', 'Bridge native code with React Native', NULL, 3, 'ADVANCED', 'C1', TRUE),
-- Docker & Kubernetes sub-courses (course 10) — IDs 25-27
(25, 10, 'Docker Fundamentals', 'Containers, images, and Dockerfiles', NULL, 1, 'BEGINNER', 'A1', TRUE),
(26, 10, 'Docker Compose', 'Multi-container applications', NULL, 2, 'INTERMEDIATE', 'B1', TRUE),
(27, 10, 'Kubernetes Basics', 'Pods, services, and deployments', NULL, 3, 'ADVANCED', 'C1', TRUE),
-- CI/CD sub-courses (course 11) — IDs 28-29
(28, 11, 'Git Workflows', 'Branching strategies and pull requests', NULL, 1, 'BEGINNER', 'A1', TRUE),
(29, 11, 'GitHub Actions', 'Automate workflows with GitHub Actions', NULL, 2, 'INTERMEDIATE', 'B1', TRUE),
-- Cybersecurity sub-courses (course 12) — IDs 30-31
(30, 12, 'Network Security Basics', 'Firewalls, VPNs, and network protocols', NULL, 1, 'BEGINNER', 'A1', TRUE),
(31, 12, 'Penetration Testing', 'Tools and techniques for pen testing', NULL, 2, 'ADVANCED', 'C1', TRUE)
ON CONFLICT (id) DO NOTHING;
-- ======================================================
-- Sub-course Videos (supplement existing 5 videos: IDs 1-5)
-- ======================================================
INSERT INTO sub_course_videos (
id, sub_course_id, title, description, video_url,
duration, resolution, visibility, display_order, status,
video_host_provider, vimeo_id, vimeo_embed_url, vimeo_status
) VALUES
-- Dart Language Basics videos (sub_course 18)
(6, 18, 'Introduction to Dart', 'Overview of Dart programming language', 'https://example.com/dart-intro.mp4', 720, '1080p', 'public', 1, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
(7, 18, 'Variables and Data Types', 'Dart variables, constants, and types', 'https://example.com/dart-variables.mp4', 900, '1080p', 'public', 2, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
(8, 18, 'Control Flow in Dart', 'If/else, loops, and switch statements', 'https://example.com/dart-control.mp4', 1100, '720p', 'public', 3, 'DRAFT', 'DIRECT', NULL, NULL, NULL),
-- Flutter UI Widgets videos (sub_course 19)
(9, 19, 'Widget Tree Basics', 'Understanding the Flutter widget tree', 'https://player.vimeo.com/video/100000001', 1500, '1080p', 'public', 1, 'PUBLISHED', 'VIMEO', '100000001', 'https://player.vimeo.com/video/100000001', 'available'),
(10, 19, 'Layout Widgets', 'Row, Column, Stack, and Container widgets', 'https://player.vimeo.com/video/100000002', 1800, '1080p', 'public', 2, 'PUBLISHED', 'VIMEO', '100000002', 'https://player.vimeo.com/video/100000002', 'available'),
(11, 19, 'Custom Widgets', 'Building reusable custom widgets', 'https://player.vimeo.com/video/100000003', 2100, '1080p', 'public', 3, 'DRAFT', 'VIMEO', '100000003', 'https://player.vimeo.com/video/100000003', 'transcoding'),
-- State Management videos (sub_course 20)
(12, 20, 'setState and Stateful Widgets', 'Managing local state in Flutter', 'https://example.com/flutter-setstate.mp4', 1200, '1080p', 'public', 1, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
(13, 20, 'Provider Pattern', 'Global state management with Provider', 'https://example.com/flutter-provider.mp4', 1600, '1080p', 'public', 2, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
-- Docker Fundamentals videos (sub_course 25)
(14, 25, 'What is Docker?', 'Introduction to containerization', 'https://example.com/docker-intro.mp4', 600, '1080p', 'public', 1, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
(15, 25, 'Building Docker Images', 'Writing Dockerfiles and building images', 'https://example.com/docker-images.mp4', 1400, '1080p', 'public', 2, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
-- Docker Compose videos (sub_course 26)
(16, 26, 'Docker Compose Basics', 'Defining multi-container applications', 'https://example.com/compose-basics.mp4', 1300, '1080p', 'public', 1, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
-- React Native Setup videos (sub_course 22)
(17, 22, 'Setting Up React Native', 'Installing React Native CLI and Expo', 'https://example.com/rn-setup.mp4', 900, '1080p', 'public', 1, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL),
(18, 22, 'Your First React Native App', 'Creating and running a basic app', 'https://example.com/rn-first-app.mp4', 1100, '1080p', 'public', 2, 'PUBLISHED', 'DIRECT', NULL, NULL, NULL)
ON CONFLICT (id) DO NOTHING;
-- ======================================================
-- Question Options for existing practice questions (17-20)
-- These were missing from the initial seed
-- ======================================================
INSERT INTO question_options (question_id, option_text, option_order, is_correct) VALUES
-- Q17: What is the correct way to print "Hello World" in Python?
(17, 'print("Hello World")', 1, TRUE),
(17, 'echo "Hello World"', 2, FALSE),
(17, 'console.log("Hello World")', 3, FALSE),
(17, 'System.out.println("Hello World")', 4, FALSE),
-- Q18: Which is a valid Python variable name?
(18, '2name', 1, FALSE),
(18, 'my_name', 2, TRUE),
(18, 'my-name', 3, FALSE),
(18, 'class', 4, FALSE),
-- Q19: How do you convert "123" to an integer?
(19, 'int("123")', 1, TRUE),
(19, 'integer("123")', 2, FALSE),
(19, 'str(123)', 3, FALSE),
(19, 'toInt("123")', 4, FALSE),
-- Q20: How many times does range(3) loop run?
(20, '2', 1, FALSE),
(20, '3', 2, TRUE),
(20, '4', 3, FALSE),
(20, '1', 4, FALSE);
-- ======================================================
-- Additional Practice Questions for new sub-courses
-- ======================================================
INSERT INTO questions (id, question_text, question_type, tips, status) VALUES
(21, 'What keyword is used to declare a variable in Dart?', 'MCQ', 'Dart uses var, final, or const', 'PUBLISHED'),
(22, 'Which widget is the root of every Flutter app?', 'MCQ', 'Think about the main() function', 'PUBLISHED'),
(23, 'What is a StatefulWidget?', 'MCQ', 'Consider mutable state', 'PUBLISHED'),
(24, 'What command creates a Docker container from an image?', 'MCQ', 'Think about docker run', 'PUBLISHED'),
(25, 'What file defines a Docker Compose application?', 'MCQ', 'It is a YAML file', 'PUBLISHED'),
(26, 'Which tool is used to create a new React Native project?', 'MCQ', 'Consider npx or expo', 'PUBLISHED')
ON CONFLICT (id) DO NOTHING;
INSERT INTO question_options (question_id, option_text, option_order, is_correct) VALUES
-- Q21: Dart variable declaration
(21, 'var', 1, TRUE),
(21, 'let', 2, FALSE),
(21, 'dim', 3, FALSE),
(21, 'define', 4, FALSE),
-- Q22: Root Flutter widget
(22, 'MaterialApp', 1, TRUE),
(22, 'Container', 2, FALSE),
(22, 'Scaffold', 3, FALSE),
(22, 'AppBar', 4, FALSE),
-- Q23: StatefulWidget
(23, 'A widget that can change its state during its lifetime', 1, TRUE),
(23, 'A widget that never changes', 2, FALSE),
(23, 'A widget for static content only', 3, FALSE),
(23, 'A widget that cannot have children', 4, FALSE),
-- Q24: Docker container creation
(24, 'docker run', 1, TRUE),
(24, 'docker create', 2, FALSE),
(24, 'docker start', 3, FALSE),
(24, 'docker build', 4, FALSE),
-- Q25: Docker Compose file
(25, 'docker-compose.yml', 1, TRUE),
(25, 'Dockerfile', 2, FALSE),
(25, 'docker.json', 3, FALSE),
(25, 'compose.xml', 4, FALSE),
-- Q26: React Native project creation
(26, 'npx react-native init', 1, TRUE),
(26, 'npm create react-native', 2, FALSE),
(26, 'react-native new', 3, FALSE),
(26, 'rn init', 4, FALSE);
-- ======================================================
-- Question Sets for new sub-courses
-- ======================================================
INSERT INTO question_sets (id, title, description, set_type, owner_type, owner_id, persona, status) VALUES
(5, 'Dart Basics Quiz', 'Test your Dart fundamentals', 'PRACTICE', 'SUB_COURSE', 18, 'beginner', 'PUBLISHED'),
(6, 'Flutter Widgets Assessment', 'Assess Flutter widget knowledge', 'PRACTICE', 'SUB_COURSE', 19, 'beginner', 'PUBLISHED'),
(7, 'State Management Quiz', 'Test state management concepts', 'PRACTICE', 'SUB_COURSE', 20, 'intermediate', 'DRAFT'),
(8, 'Docker Fundamentals Quiz', 'Test Docker basics', 'PRACTICE', 'SUB_COURSE', 25, 'beginner', 'PUBLISHED'),
(9, 'Docker Compose Assessment', 'Assess Docker Compose skills', 'PRACTICE', 'SUB_COURSE', 26, 'intermediate', 'PUBLISHED'),
(10, 'React Native Setup Quiz', 'Test React Native setup knowledge', 'PRACTICE', 'SUB_COURSE', 22, 'beginner', 'PUBLISHED')
ON CONFLICT (id) DO NOTHING;
-- Ensure every sub-course has at least one practice set
INSERT INTO question_sets (title, description, set_type, owner_type, owner_id, status)
SELECT
sc.title || ' Practice',
'Default practice set for ' || sc.title,
'PRACTICE',
'SUB_COURSE',
sc.id,
'DRAFT'
FROM sub_courses sc
WHERE NOT EXISTS (
SELECT 1
FROM question_sets qs
WHERE qs.owner_type = 'SUB_COURSE'
AND qs.owner_id = sc.id
AND qs.set_type = 'PRACTICE'
AND qs.status != 'ARCHIVED'
);
-- Ensure every sub-course has one initial assessment set
INSERT INTO question_sets (title, description, set_type, owner_type, owner_id, status)
SELECT
sc.title || ' Entry Assessment',
'Initial assessment used before learners start ' || sc.title,
'INITIAL_ASSESSMENT',
'SUB_COURSE',
sc.id,
'DRAFT'
FROM sub_courses sc
WHERE NOT EXISTS (
SELECT 1
FROM question_sets qs
WHERE qs.owner_type = 'SUB_COURSE'
AND qs.owner_id = sc.id
AND qs.set_type = 'INITIAL_ASSESSMENT'
AND qs.status != 'ARCHIVED'
);
-- Link questions to question sets
INSERT INTO question_set_items (set_id, question_id, display_order) VALUES
(5, 21, 1),
(6, 22, 1),
(7, 23, 1),
(8, 24, 1),
(9, 25, 1),
(10, 26, 1)
ON CONFLICT (set_id, question_id) DO NOTHING;
-- Link personas to question sets
INSERT INTO question_set_personas (question_set_id, user_id, display_order) VALUES
(5, 10, 1), (5, 11, 2),
(6, 10, 1), (6, 12, 2),
(8, 11, 1),
(10, 10, 1)
ON CONFLICT (question_set_id, user_id) DO NOTHING;
-- ======================================================
-- Sub-course Prerequisites
-- Defines the learning path / dependency graph
-- ======================================================
INSERT INTO sub_course_prerequisites (sub_course_id, prerequisite_sub_course_id) VALUES
-- Python course (IDs 1-5): linear progression
-- "Python Basics - Data Types" requires "Python Basics - Getting Started"
(2, 1),
-- "Python Intermediate - Functions" requires "Python Basics - Data Types"
(3, 2),
-- "Python Intermediate - Collections" requires "Python Intermediate - Functions"
(4, 3),
-- "Python Advanced - Best Practices" requires "Python Intermediate - Collections"
(5, 4),
-- JavaScript course (IDs 6-7): linear
-- "DOM Manipulation Basics" requires "JavaScript Fundamentals"
(7, 6),
-- Java course (IDs 8-9): linear
-- "Spring Framework Intro" requires "Java Core Concepts"
(9, 8),
-- Data Science course (IDs 10-11): linear
-- "Advanced Data Analysis" requires "Data Analysis Fundamentals"
(11, 10),
-- ML course (IDs 12-13): linear
-- "ML Algorithms" requires "ML Basics"
(13, 12),
-- Full Stack course (IDs 14-15): linear
-- "Backend Development" requires "Frontend Fundamentals"
(15, 14),
-- React course (IDs 16-17): linear
-- "React Advanced Patterns" requires "React Basics"
(17, 16),
-- Flutter course (IDs 18-21): structured path
-- "Flutter UI Widgets" requires "Dart Language Basics"
(19, 18),
-- "State Management" requires "Flutter UI Widgets"
(20, 19),
-- "Flutter Networking & APIs" requires "State Management"
(21, 20),
-- React Native course (IDs 22-24): linear
-- "Navigation & Routing" requires "React Native Setup"
(23, 22),
-- "Native Modules" requires "Navigation & Routing"
(24, 23),
-- Docker & Kubernetes course (IDs 25-27): structured
-- "Docker Compose" requires "Docker Fundamentals"
(26, 25),
-- "Kubernetes Basics" requires "Docker Compose"
(27, 26),
-- CI/CD course (IDs 28-29): linear
-- "GitHub Actions" requires "Git Workflows"
(29, 28),
-- Cybersecurity course (IDs 30-31): linear
-- "Penetration Testing" requires "Network Security Basics"
(31, 30)
ON CONFLICT (sub_course_id, prerequisite_sub_course_id) DO NOTHING;
-- ======================================================
-- Completion-driven progress seed (auto-aggregate model)
-- Seed video/practice completion records, then derive sub-course progress
-- ======================================================
-- Video completions
INSERT INTO user_sub_course_video_progress (user_id, sub_course_id, video_id, completed_at, updated_at)
SELECT 10, v.sub_course_id, v.id, CURRENT_TIMESTAMP - INTERVAL '20 days', CURRENT_TIMESTAMP - INTERVAL '20 days'
FROM sub_course_videos v
WHERE v.sub_course_id IN (1, 2, 18)
AND v.status = 'PUBLISHED'
ON CONFLICT (user_id, video_id) DO NOTHING;
INSERT INTO user_sub_course_video_progress (user_id, sub_course_id, video_id, completed_at, updated_at)
SELECT 10, v.sub_course_id, v.id, CURRENT_TIMESTAMP - INTERVAL '8 days', CURRENT_TIMESTAMP - INTERVAL '8 days'
FROM sub_course_videos v
WHERE v.sub_course_id = 19
AND v.status = 'PUBLISHED'
AND v.display_order = 1
ON CONFLICT (user_id, video_id) DO NOTHING;
INSERT INTO user_sub_course_video_progress (user_id, sub_course_id, video_id, completed_at, updated_at)
SELECT 11, v.sub_course_id, v.id, CURRENT_TIMESTAMP - INTERVAL '25 days', CURRENT_TIMESTAMP - INTERVAL '25 days'
FROM sub_course_videos v
WHERE v.sub_course_id IN (1, 2, 25)
AND v.status = 'PUBLISHED'
ON CONFLICT (user_id, video_id) DO NOTHING;
INSERT INTO user_sub_course_video_progress (user_id, sub_course_id, video_id, completed_at, updated_at)
SELECT 11, v.sub_course_id, v.id, CURRENT_TIMESTAMP - INTERVAL '3 days', CURRENT_TIMESTAMP - INTERVAL '3 days'
FROM sub_course_videos v
WHERE v.sub_course_id = 26
AND v.status = 'PUBLISHED'
ON CONFLICT (user_id, video_id) DO NOTHING;
INSERT INTO user_sub_course_video_progress (user_id, sub_course_id, video_id, completed_at, updated_at)
SELECT 12, v.sub_course_id, v.id, CURRENT_TIMESTAMP - INTERVAL '7 days', CURRENT_TIMESTAMP - INTERVAL '7 days'
FROM sub_course_videos v
WHERE v.sub_course_id = 22
AND v.status = 'PUBLISHED'
ON CONFLICT (user_id, video_id) DO NOTHING;
INSERT INTO user_sub_course_video_progress (user_id, sub_course_id, video_id, completed_at, updated_at)
SELECT 12, v.sub_course_id, v.id, CURRENT_TIMESTAMP - INTERVAL '3 days', CURRENT_TIMESTAMP - INTERVAL '3 days'
FROM sub_course_videos v
WHERE v.sub_course_id = 18
AND v.status = 'PUBLISHED'
AND v.display_order = 1
ON CONFLICT (user_id, video_id) DO NOTHING;
-- Practice completions
INSERT INTO user_practice_progress (user_id, sub_course_id, question_set_id, completed_at, updated_at)
SELECT 10, qs.owner_id::BIGINT, qs.id, CURRENT_TIMESTAMP - INTERVAL '18 days', CURRENT_TIMESTAMP - INTERVAL '18 days'
FROM question_sets qs
WHERE qs.owner_type = 'SUB_COURSE'
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND qs.owner_id IN (1, 2, 18)
ON CONFLICT (user_id, question_set_id) DO NOTHING;
INSERT INTO user_practice_progress (user_id, sub_course_id, question_set_id, completed_at, updated_at)
SELECT 11, qs.owner_id::BIGINT, qs.id, CURRENT_TIMESTAMP - INTERVAL '10 days', CURRENT_TIMESTAMP - INTERVAL '10 days'
FROM question_sets qs
WHERE qs.owner_type = 'SUB_COURSE'
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND qs.owner_id IN (1, 2, 25)
ON CONFLICT (user_id, question_set_id) DO NOTHING;
INSERT INTO user_practice_progress (user_id, sub_course_id, question_set_id, completed_at, updated_at)
SELECT 12, qs.owner_id::BIGINT, qs.id, CURRENT_TIMESTAMP - INTERVAL '7 days', CURRENT_TIMESTAMP - INTERVAL '7 days'
FROM question_sets qs
WHERE qs.owner_type = 'SUB_COURSE'
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND qs.owner_id IN (22)
ON CONFLICT (user_id, question_set_id) DO NOTHING;
-- Derive sub-course progress from completion tables (same model as runtime auto-aggregate)
WITH target_pairs AS (
SELECT DISTINCT user_id, sub_course_id
FROM user_sub_course_video_progress
WHERE user_id IN (10, 11, 12)
UNION
SELECT DISTINCT user_id, sub_course_id
FROM user_practice_progress
WHERE user_id IN (10, 11, 12)
),
stats AS (
SELECT
tp.user_id,
tp.sub_course_id,
(SELECT COUNT(*)::INT
FROM sub_course_videos v
WHERE v.sub_course_id = tp.sub_course_id
AND v.status = 'PUBLISHED')
+
(SELECT COUNT(*)::INT
FROM question_sets qs
WHERE qs.owner_type = 'SUB_COURSE'
AND qs.owner_id = tp.sub_course_id
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED') AS total_items,
(SELECT COUNT(*)::INT
FROM user_sub_course_video_progress uv
JOIN sub_course_videos v ON v.id = uv.video_id
WHERE uv.user_id = tp.user_id
AND uv.sub_course_id = tp.sub_course_id
AND uv.completed_at IS NOT NULL
AND v.status = 'PUBLISHED')
+
(SELECT COUNT(*)::INT
FROM user_practice_progress up
JOIN question_sets qs ON qs.id = up.question_set_id
WHERE up.user_id = tp.user_id
AND up.sub_course_id = tp.sub_course_id
AND up.completed_at IS NOT NULL
AND qs.owner_type = 'SUB_COURSE'
AND qs.owner_id = tp.sub_course_id
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED') AS completed_items
FROM target_pairs tp
)
INSERT INTO user_sub_course_progress (user_id, sub_course_id, status, progress_percentage, started_at, completed_at, updated_at)
SELECT
user_id,
sub_course_id,
CASE
WHEN total_items > 0 AND completed_items >= total_items THEN 'COMPLETED'
WHEN completed_items > 0 THEN 'IN_PROGRESS'
ELSE 'NOT_STARTED'
END AS status,
CASE
WHEN total_items = 0 THEN 0
ELSE ROUND((completed_items::NUMERIC * 100.0) / total_items::NUMERIC)::SMALLINT
END AS progress_percentage,
CASE WHEN completed_items > 0 THEN CURRENT_TIMESTAMP - INTERVAL '10 days' ELSE NULL END AS started_at,
CASE WHEN total_items > 0 AND completed_items >= total_items THEN CURRENT_TIMESTAMP - INTERVAL '3 days' ELSE NULL END AS completed_at,
CURRENT_TIMESTAMP AS updated_at
FROM stats
ON CONFLICT (user_id, sub_course_id) DO UPDATE SET
status = EXCLUDED.status,
progress_percentage = EXCLUDED.progress_percentage,
started_at = COALESCE(user_sub_course_progress.started_at, EXCLUDED.started_at),
completed_at = EXCLUDED.completed_at,
updated_at = EXCLUDED.updated_at;
-- ======================================================
-- Reset sequences to avoid ID conflicts after seeding
-- ======================================================
SELECT setval(pg_get_serial_sequence('course_categories', 'id'), COALESCE((SELECT MAX(id) FROM course_categories), 1), true);
SELECT setval(pg_get_serial_sequence('courses', 'id'), COALESCE((SELECT MAX(id) FROM courses), 1), true);
SELECT setval(pg_get_serial_sequence('sub_courses', 'id'), COALESCE((SELECT MAX(id) FROM sub_courses), 1), true);
SELECT setval(pg_get_serial_sequence('sub_course_videos', 'id'), COALESCE((SELECT MAX(id) FROM sub_course_videos), 1), true);
SELECT setval(pg_get_serial_sequence('questions', 'id'), COALESCE((SELECT MAX(id) FROM questions), 1), true);
SELECT setval(pg_get_serial_sequence('question_options', 'id'), COALESCE((SELECT MAX(id) FROM question_options), 1), true);
SELECT setval(pg_get_serial_sequence('question_sets', 'id'), COALESCE((SELECT MAX(id) FROM question_sets), 1), true);
SELECT setval(pg_get_serial_sequence('question_set_items', 'id'), COALESCE((SELECT MAX(id) FROM question_set_items), 1), true);
SELECT setval(pg_get_serial_sequence('question_set_personas', 'id'), COALESCE((SELECT MAX(id) FROM question_set_personas), 1), true);
SELECT setval(pg_get_serial_sequence('sub_course_prerequisites', 'id'), COALESCE((SELECT MAX(id) FROM sub_course_prerequisites), 1), true);
SELECT setval(pg_get_serial_sequence('user_sub_course_progress', 'id'), COALESCE((SELECT MAX(id) FROM user_sub_course_progress), 1), true);
SELECT setval(pg_get_serial_sequence('user_sub_course_video_progress', 'id'), COALESCE((SELECT MAX(id) FROM user_sub_course_video_progress), 1), true);
SELECT setval(pg_get_serial_sequence('user_practice_progress', 'id'), COALESCE((SELECT MAX(id) FROM user_practice_progress), 1), true);

View File

@ -1,29 +1 @@
-- Seed account deletion request states for admin panel tracking -- Intentionally empty: no demo account-deletion seed (login-only seed in 001).
-- Users referenced here are seeded in 001_initial_seed_data.sql (IDs: 10, 11, 12).
-- Pending deletion request (within grace period)
UPDATE users
SET
deletion_requested_at = now() - interval '2 days',
deletion_scheduled_at = now() + interval '13 days',
deletion_cancelled_at = NULL,
updated_at = now()
WHERE id = 10;
-- Due deletion request (grace period elapsed, awaiting purge worker)
UPDATE users
SET
deletion_requested_at = now() - interval '20 days',
deletion_scheduled_at = now() - interval '5 days',
deletion_cancelled_at = NULL,
updated_at = now()
WHERE id = 11;
-- Cancelled deletion request (request made then cancelled)
UPDATE users
SET
deletion_requested_at = now() - interval '10 days',
deletion_scheduled_at = now() + interval '5 days',
deletion_cancelled_at = now() - interval '3 days',
updated_at = now()
WHERE id = 12;

View File

@ -1,67 +1 @@
-- Seed TRUE_FALSE and SHORT_ANSWER question types -- Intentionally empty: no demo question seed (login-only seed in 001).
-- Ensures question sets contain non-MCQ questions for end-to-end testing.
-- ======================================================
-- TRUE_FALSE questions (stored in questions + question_options)
-- ======================================================
INSERT INTO questions (
id,
question_text,
question_type,
difficulty_level,
points,
status,
created_at
)
VALUES
(27, 'The Python interpreter executes Python code top-to-bottom.', 'TRUE_FALSE', 'EASY', 1, 'PUBLISHED', CURRENT_TIMESTAMP)
ON CONFLICT (id) DO NOTHING;
-- question_options for TRUE_FALSE: use two options with exactly one correct
INSERT INTO question_options (question_id, option_text, option_order, is_correct)
VALUES
(27, 'True', 1, TRUE),
(27, 'False', 2, FALSE)
ON CONFLICT DO NOTHING;
-- ======================================================
-- SHORT_ANSWER questions (stored in questions + question_short_answers)
-- ======================================================
INSERT INTO questions (
id,
question_text,
question_type,
difficulty_level,
points,
status,
created_at
)
VALUES
(29, 'What keyword is used in Python to define a function?', 'SHORT_ANSWER', 'EASY', 1, 'PUBLISHED', CURRENT_TIMESTAMP)
ON CONFLICT (id) DO NOTHING;
INSERT INTO question_short_answers (question_id, acceptable_answer, match_type)
VALUES
(29, 'def', 'EXACT')
ON CONFLICT DO NOTHING;
-- ======================================================
-- Link new questions into existing question sets
-- Question Set 1: Initial Assessment (set_id = 1, PUBLISHED)
-- Question Set 2: Python Basics Assessment (set_id = 2, PUBLISHED)
-- ======================================================
INSERT INTO question_set_items (set_id, question_id, display_order)
VALUES
(1, 27, 17),
(1, 29, 18),
(2, 27, 3),
(2, 29, 4)
ON CONFLICT (set_id, question_id) DO NOTHING;
-- ======================================================
-- Reset sequences to avoid ID collisions after seeding
-- ======================================================
SELECT setval(pg_get_serial_sequence('questions', 'id'), COALESCE((SELECT MAX(id) FROM questions), 1), true);
SELECT setval(pg_get_serial_sequence('question_options', 'id'), COALESCE((SELECT MAX(id) FROM question_options), 1), true);
SELECT setval(pg_get_serial_sequence('question_short_answers', 'id'), COALESCE((SELECT MAX(id) FROM question_short_answers), 1), true);

View File

@ -1250,7 +1250,7 @@ const docTemplate = `{
}, },
"/api/v1/internal/db/reset-reseed": { "/api/v1/internal/db/reset-reseed": {
"post": { "post": {
"description": "Dangerous operation: clears and reseeds only course_categories, courses, and sub_courses from seed SQL files.", "description": "Truncates course_categories, courses, and sub_courses. If seed SQL contains INSERTs for those tables (e.g. 007_course_management_seed.sql), they are replayed; otherwise tables are left empty after truncate.",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],

View File

@ -1242,7 +1242,7 @@
}, },
"/api/v1/internal/db/reset-reseed": { "/api/v1/internal/db/reset-reseed": {
"post": { "post": {
"description": "Dangerous operation: clears and reseeds only course_categories, courses, and sub_courses from seed SQL files.", "description": "Truncates course_categories, courses, and sub_courses. If seed SQL contains INSERTs for those tables (e.g. 007_course_management_seed.sql), they are replayed; otherwise tables are left empty after truncate.",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],

View File

@ -2678,8 +2678,9 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: 'Dangerous operation: clears and reseeds only course_categories, description: Truncates course_categories, courses, and sub_courses. If seed
courses, and sub_courses from seed SQL files.' SQL contains INSERTs for those tables (e.g. 007_course_management_seed.sql),
they are replayed; otherwise tables are left empty after truncate.
parameters: parameters:
- description: Reset token configured in DB_RESET_RESEED_TOKEN - description: Reset token configured in DB_RESET_RESEED_TOKEN
in: header in: header

View File

@ -78,7 +78,7 @@ func resolveSeedDir(seedDir string) (string, error) {
// ResetAndReseedDatabase godoc // ResetAndReseedDatabase godoc
// @Summary Reset and reseed database // @Summary Reset and reseed database
// @Description Dangerous operation: clears and reseeds only course_categories, courses, and sub_courses from seed SQL files. // @Description Truncates course_categories, courses, and sub_courses. If seed SQL contains INSERTs for those tables (e.g. 007_course_management_seed.sql), they are replayed; otherwise tables are left empty after truncate.
// @Tags internal // @Tags internal
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -154,18 +154,23 @@ func (h *Handler) ResetAndReseedDatabase(c *fiber.Ctx) error {
} }
} }
missing := 0
for _, tableName := range tableNames { for _, tableName := range tableNames {
if _, ok := statements[tableName]; !ok { if _, ok := statements[tableName]; !ok {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ missing++
Message: "Missing required seed statement",
Error: fmt.Sprintf("could not find INSERT INTO %s in seed files", tableName),
})
} }
} }
if missing != 0 && missing != len(tableNames) {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Incomplete course seed SQL",
Error: "seed files must define INSERT for all of course_categories, courses, and sub_courses, or none of them (truncate-only)",
})
}
var sqlBuilder strings.Builder var sqlBuilder strings.Builder
sqlBuilder.WriteString("BEGIN;\n") sqlBuilder.WriteString("BEGIN;\n")
sqlBuilder.WriteString("TRUNCATE TABLE sub_courses, courses, course_categories RESTART IDENTITY CASCADE;\n") sqlBuilder.WriteString("TRUNCATE TABLE sub_courses, courses, course_categories RESTART IDENTITY CASCADE;\n")
if missing == 0 {
for _, tableName := range tableNames { for _, tableName := range tableNames {
sqlBuilder.WriteString("\n-- ") sqlBuilder.WriteString("\n-- ")
sqlBuilder.WriteString(tableName) sqlBuilder.WriteString(tableName)
@ -175,6 +180,7 @@ func (h *Handler) ResetAndReseedDatabase(c *fiber.Ctx) error {
sqlBuilder.WriteString(statements[tableName]) sqlBuilder.WriteString(statements[tableName])
sqlBuilder.WriteString("\n") sqlBuilder.WriteString("\n")
} }
}
sqlBuilder.WriteString("COMMIT;") sqlBuilder.WriteString("COMMIT;")
if _, err := h.analyticsDB.ExecRaw(c.Context(), sqlBuilder.String()); err != nil { if _, err := h.analyticsDB.ExecRaw(c.Context(), sqlBuilder.String()); err != nil {
@ -184,12 +190,18 @@ func (h *Handler) ResetAndReseedDatabase(c *fiber.Ctx) error {
}) })
} }
msg := "Course management hierarchy reset and reseed completed successfully"
if missing == len(tableNames) {
msg = "Course management hierarchy truncated successfully (no INSERT seed configured; tables empty)"
}
return c.JSON(domain.Response{ return c.JSON(domain.Response{
Message: "Course management hierarchy reset and reseed completed successfully", Message: msg,
Data: map[string]interface{}{ Data: map[string]interface{}{
"seed_dir": seedDir, "seed_dir": seedDir,
"tables": tableNames, "tables": tableNames,
"sources": statementSource, "sources": statementSource,
"reseeded": missing == 0,
"truncate_only": missing == len(tableNames),
}, },
Success: true, Success: true,
StatusCode: fiber.StatusOK, StatusCode: fiber.StatusOK,

View File

@ -66,10 +66,6 @@ seed_data:
sleep 1; \ sleep 1; \
done done
@for file in db/data/*.sql; do \ @for file in db/data/*.sql; do \
if [ "$$(basename $$file)" = "007_course_management_seed.sql" ]; then \
echo "Skipping $$file (course management seed disabled)"; \
continue; \
fi; \
echo "Seeding $$file..."; \ echo "Seeding $$file..."; \
cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \ cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \
done done