diff --git a/db/data/001_initial_seed_data.sql b/db/data/001_initial_seed_data.sql index 524d88f..ce9947f 100644 --- a/db/data/001_initial_seed_data.sql +++ b/db/data/001_initial_seed_data.sql @@ -136,190 +136,6 @@ VALUES ) 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) -- Credentials: email + password@123 @@ -471,3 +287,8 @@ VALUES CURRENT_TIMESTAMP ) 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'; diff --git a/db/data/003_fix_autoincrement_desync.sql b/db/data/003_fix_autoincrement_desync.sql index a1a7b60..fd2723e 100644 --- a/db/data/003_fix_autoincrement_desync.sql +++ b/db/data/003_fix_autoincrement_desync.sql @@ -1,108 +1,25 @@ --- ====================================================== --- Reset sequences for LMS tables (PostgreSQL) --- ====================================================== +-- Reset sequences for tables touched by login-only seed (PostgreSQL) --- users.id (BIGSERIAL) SELECT setval( pg_get_serial_sequence('users', 'id'), COALESCE((SELECT MAX(id) FROM users), 1), true ); --- questions.id (BIGSERIAL) SELECT setval( - pg_get_serial_sequence('questions', 'id'), - COALESCE((SELECT MAX(id) FROM questions), 1), + pg_get_serial_sequence('team_members', 'id'), + COALESCE((SELECT MAX(id) FROM team_members), 1), 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( pg_get_serial_sequence('refresh_tokens', 'id'), COALESCE((SELECT MAX(id) FROM refresh_tokens), 1), true ); --- otps.id (BIGSERIAL) SELECT setval( pg_get_serial_sequence('otps', 'id'), COALESCE((SELECT MAX(id) FROM otps), 1), 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 -); diff --git a/db/data/004_activity_logs_seed.sql b/db/data/004_activity_logs_seed.sql index 6405acf..feb55fb 100644 --- a/db/data/004_activity_logs_seed.sql +++ b/db/data/004_activity_logs_seed.sql @@ -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 -(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'); +-- Intentionally empty: no demo activity log seed (login-only seed in 001). diff --git a/db/data/005_issue_reporting_seed.sql b/db/data/005_issue_reporting_seed.sql index a17e8cf..f3d136d 100644 --- a/db/data/005_issue_reporting_seed.sql +++ b/db/data/005_issue_reporting_seed.sql @@ -1,14 +1 @@ -INSERT INTO reported_issues (user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at) VALUES -(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; +-- Intentionally empty: no demo issue-report seed (login-only seed in 001). diff --git a/db/data/006_notifications_seed.sql b/db/data/006_notifications_seed.sql index 2f13bf9..243abcf 100644 --- a/db/data/006_notifications_seed.sql +++ b/db/data/006_notifications_seed.sql @@ -1,40 +1 @@ -INSERT INTO notifications ( - 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.', '
Your weekly course progress summary is ready.
', 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.', 'Some users may experience delayed payment confirmation.
', 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; +-- Intentionally empty: no demo notification seed (login-only seed in 001). diff --git a/db/data/007_course_management_seed.sql b/db/data/007_course_management_seed.sql index 2b989bb..80cc0d3 100644 --- a/db/data/007_course_management_seed.sql +++ b/db/data/007_course_management_seed.sql @@ -1,469 +1,2 @@ --- ====================================================== --- Complete Course Management Seed Data --- 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); +-- Intentionally empty: course hierarchy is not seeded from SQL. +-- Use admin/API or migrations to create content. diff --git a/db/data/008_account_deletion_requests_seed.sql b/db/data/008_account_deletion_requests_seed.sql index 1918e74..306b1a7 100644 --- a/db/data/008_account_deletion_requests_seed.sql +++ b/db/data/008_account_deletion_requests_seed.sql @@ -1,29 +1 @@ --- Seed account deletion request states for admin panel tracking --- 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; +-- Intentionally empty: no demo account-deletion seed (login-only seed in 001). diff --git a/db/data/009_question_types_seed.sql b/db/data/009_question_types_seed.sql index 8bb928d..9a55189 100644 --- a/db/data/009_question_types_seed.sql +++ b/db/data/009_question_types_seed.sql @@ -1,67 +1 @@ --- Seed TRUE_FALSE and SHORT_ANSWER question types --- 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); - +-- Intentionally empty: no demo question seed (login-only seed in 001). diff --git a/docs/docs.go b/docs/docs.go index a2a6c09..ebce5d4 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1250,7 +1250,7 @@ const docTemplate = `{ }, "/api/v1/internal/db/reset-reseed": { "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": [ "application/json" ], diff --git a/docs/swagger.json b/docs/swagger.json index d2a1bef..10ef9ed 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1242,7 +1242,7 @@ }, "/api/v1/internal/db/reset-reseed": { "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": [ "application/json" ], diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5336f56..bb8af8c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2678,8 +2678,9 @@ paths: post: consumes: - application/json - 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. parameters: - description: Reset token configured in DB_RESET_RESEED_TOKEN in: header diff --git a/internal/web_server/handlers/maintenance_handler.go b/internal/web_server/handlers/maintenance_handler.go index d997360..d9d87aa 100644 --- a/internal/web_server/handlers/maintenance_handler.go +++ b/internal/web_server/handlers/maintenance_handler.go @@ -78,7 +78,7 @@ func resolveSeedDir(seedDir string) (string, error) { // ResetAndReseedDatabase godoc // @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 // @Accept json // @Produce json @@ -154,26 +154,32 @@ func (h *Handler) ResetAndReseedDatabase(c *fiber.Ctx) error { } } + missing := 0 for _, tableName := range tableNames { if _, ok := statements[tableName]; !ok { - return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ - Message: "Missing required seed statement", - Error: fmt.Sprintf("could not find INSERT INTO %s in seed files", tableName), - }) + missing++ } } + 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 sqlBuilder.WriteString("BEGIN;\n") sqlBuilder.WriteString("TRUNCATE TABLE sub_courses, courses, course_categories RESTART IDENTITY CASCADE;\n") - for _, tableName := range tableNames { - sqlBuilder.WriteString("\n-- ") - sqlBuilder.WriteString(tableName) - sqlBuilder.WriteString(" from ") - sqlBuilder.WriteString(statementSource[tableName]) - sqlBuilder.WriteString("\n") - sqlBuilder.WriteString(statements[tableName]) - sqlBuilder.WriteString("\n") + if missing == 0 { + for _, tableName := range tableNames { + sqlBuilder.WriteString("\n-- ") + sqlBuilder.WriteString(tableName) + sqlBuilder.WriteString(" from ") + sqlBuilder.WriteString(statementSource[tableName]) + sqlBuilder.WriteString("\n") + sqlBuilder.WriteString(statements[tableName]) + sqlBuilder.WriteString("\n") + } } sqlBuilder.WriteString("COMMIT;") @@ -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{ - Message: "Course management hierarchy reset and reseed completed successfully", + Message: msg, Data: map[string]interface{}{ - "seed_dir": seedDir, - "tables": tableNames, - "sources": statementSource, + "seed_dir": seedDir, + "tables": tableNames, + "sources": statementSource, + "reseeded": missing == 0, + "truncate_only": missing == len(tableNames), }, Success: true, StatusCode: fiber.StatusOK, diff --git a/makefile b/makefile index 985a578..fba54ad 100644 --- a/makefile +++ b/makefile @@ -66,10 +66,6 @@ seed_data: sleep 1; \ done @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..."; \ cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \ done