From 575e4ef1986616f4b839f1be07b06537ba36a2bb Mon Sep 17 00:00:00 2001 From: wielopolski Date: Mon, 2 Dec 2024 16:23:45 +0100 Subject: [PATCH] feat: update seed, add teacher stats --- apps/api/src/seed.ts | 95 +- .../repositories/statistics.repository.ts | 8 +- ...id_constraint_on_course_students_stats.sql | 1 + .../migrations/meta/0015_snapshot.json | 2274 +++++++++++++++++ .../src/storage/migrations/meta/_journal.json | 7 + apps/api/src/storage/schema/index.ts | 3 - apps/web/app/api/generated-api.ts | 2 +- 7 files changed, 2376 insertions(+), 14 deletions(-) create mode 100644 apps/api/src/storage/migrations/0015_drop_course_id_constraint_on_course_students_stats.sql create mode 100644 apps/api/src/storage/migrations/meta/0015_snapshot.json diff --git a/apps/api/src/seed.ts b/apps/api/src/seed.ts index 1882d9cd4..008aeb380 100644 --- a/apps/api/src/seed.ts +++ b/apps/api/src/seed.ts @@ -1,9 +1,9 @@ import { faker } from "@faker-js/faker"; import { format, subMonths } from "date-fns"; import * as dotenv from "dotenv"; -import { eq, sql } from "drizzle-orm"; +import { eq, sql, and } from "drizzle-orm"; import { drizzle } from "drizzle-orm/postgres-js"; -import { now, sampleSize } from "lodash"; +import { flatMap, now, sampleSize } from "lodash"; import postgres from "postgres"; import hashPassword from "./common/helpers/hashPassword"; @@ -16,12 +16,15 @@ import { categories, courseLessons, courses, + coursesSummaryStats, + courseStudentsStats, credentials, files, lessonItems, lessons, questionAnswerOptions, questions, + quizAttempts, studentCourses, studentLessonsProgress, textBlocks, @@ -315,6 +318,80 @@ async function createLessonProgress(userId: string) { return db.insert(studentLessonsProgress).values(lessonProgressList).returning(); } +async function createCoursesSummaryStats(courses: any[] = []) { + const createdCoursesSummaryStats = courses.map((course) => ({ + authorId: course.authorId, + courseId: course.id, + freePurchasedCount: faker.number.int({ min: 20, max: 40 }), + paidPurchasedCount: faker.number.int({ min: 20, max: 40 }), + paidPurchasedAfterFreemiumCount: faker.number.int({ min: 0, max: 20 }), + completedFreemiumStudentCount: faker.number.int({ min: 40, max: 60 }), + completedCourseStudentCount: faker.number.int({ min: 0, max: 20 }), + })); + + return db.insert(coursesSummaryStats).values(createdCoursesSummaryStats); +} + +async function createQuizAttempts(userId: string) { + const quizzes = await db + .select({ courseId: courses.id, lessonId: lessons.id, lessonItemsCount: lessons.itemsCount }) + .from(courses) + .innerJoin(courseLessons, eq(courses.id, courseLessons.courseId)) + .innerJoin(lessons, eq(courseLessons.lessonId, lessons.id)) + .where(and(eq(courses.state, STATES.published), eq(lessons.type, LESSON_TYPE.quiz.key))); + + const createdQuizAttempts = quizzes.map((quiz) => { + const correctAnswers = faker.number.int({ min: 0, max: quiz.lessonItemsCount }); + + return { + userId, + courseId: quiz.courseId, + lessonId: quiz.lessonId, + correctAnswers: correctAnswers, + wrongAnswers: quiz.lessonItemsCount - correctAnswers, + score: Math.round((correctAnswers / quiz.lessonItemsCount) * 100), + }; + }); + + return db.insert(quizAttempts).values(createdQuizAttempts); +} + +function getLast12Months(): Array<{ month: number; year: number; formattedDate: string }> { + const today = new Date(); + return Array.from({ length: 12 }, (_, index) => { + const date = subMonths(today, index); + return { + month: date.getMonth(), + year: date.getFullYear(), + formattedDate: format(date, "MMMM yyyy"), + }; + }).reverse(); +} + +async function createCourseStudentsStats() { + const createdCourses = await db + .select({ + courseId: courses.id, + authorId: courses.authorId, + }) + .from(courses) + .where(eq(courses.state, STATES.published)); + + const twelveMonthsAgoArray = getLast12Months(); + + const createdTwelveMonthsAgoStats = flatMap(createdCourses, (course) => + twelveMonthsAgoArray.map((monthDetails) => ({ + courseId: course.courseId, + authorId: course.authorId, + newStudentsCount: faker.number.int({ min: 5, max: 25 }), + month: monthDetails.month, + year: monthDetails.year, + })), + ); + + await db.insert(courseStudentsStats).values(createdTwelveMonthsAgoStats); +} + async function seed() { await seedTruncateAllTables(db); @@ -339,9 +416,9 @@ async function seed() { role: USER_ROLES.student, }); - const tutorUser = await createOrFindUser("tutor@example.com", "password", { + const teacherUser = await createOrFindUser("teacher@example.com", "password", { id: faker.string.uuid(), - email: "tutor@example.com", + email: "teacher@example.com", firstName: "Tutor", lastName: "User", createdAt: new Date().toISOString(), @@ -351,7 +428,7 @@ async function seed() { console.log("Created or found admin user:", adminUser); console.log("Created or found student user:", studentUser); - console.log("Created or found tutor user:", tutorUser); + console.log("Created or found teacher user:", teacherUser); await createUsers(5); console.log("Created users with credentials"); @@ -464,7 +541,7 @@ async function seed() { console.log("Created files"); const createdCourses = await createCoursesWithLessons( - [adminUser.id, tutorUser.id], + [adminUser.id, teacherUser.id], createdCategories, createdFilesStatePublished, createdTextBlocksStatePublished, @@ -478,6 +555,12 @@ async function seed() { await createStudentCourses(selectedCourses, studentUser.id); console.log("Created student courses"); await createLessonProgress(studentUser.id); + console.log("Created student lesson progress"); + // TODO: change to function working on data from database as in real app + await createCoursesSummaryStats(selectedCourses); + await createQuizAttempts(studentUser.id); + await createCourseStudentsStats(); + console.log("Created student course students stats"); console.log("Seeding completed successfully"); } catch (error) { diff --git a/apps/api/src/statistics/repositories/statistics.repository.ts b/apps/api/src/statistics/repositories/statistics.repository.ts index 0a00bdcb5..8e9f0f56b 100644 --- a/apps/api/src/statistics/repositories/statistics.repository.ts +++ b/apps/api/src/statistics/repositories/statistics.repository.ts @@ -156,7 +156,7 @@ export class StatisticsRepository { .select({ totalCoursesCompletion: sql`COALESCE(SUM(${coursesSummaryStats.completedCourseStudentCount}), 0)::INTEGER`, totalCourses: sql`COALESCE(SUM(${coursesSummaryStats.freePurchasedCount} + ${coursesSummaryStats.paidPurchasedCount}), 0)::INTEGER`, - completionPercentage: sql`COALESCE(SUM(${coursesSummaryStats.completedCourseStudentCount}) / SUM(${coursesSummaryStats.freePurchasedCount} + ${coursesSummaryStats.paidPurchasedCount}), 0)::INTEGER`, + completionPercentage: sql`(SUM(${coursesSummaryStats.completedCourseStudentCount})::DECIMAL / (SUM(${coursesSummaryStats.freePurchasedCount} + ${coursesSummaryStats.paidPurchasedCount})) * 100)::INTEGER`, }) .from(coursesSummaryStats) .where(eq(coursesSummaryStats.authorId, userId)); @@ -167,7 +167,7 @@ export class StatisticsRepository { .select({ purchasedCourses: sql`COALESCE(SUM(${coursesSummaryStats.paidPurchasedAfterFreemiumCount}), 0)::INTEGER`, remainedOnFreemium: sql`COALESCE(SUM(${coursesSummaryStats.completedFreemiumStudentCount} - ${coursesSummaryStats.paidPurchasedAfterFreemiumCount}), 0)::INTEGER`, - conversionPercentage: sql`COALESCE(SUM(${coursesSummaryStats.paidPurchasedAfterFreemiumCount}) / SUM(${coursesSummaryStats.completedFreemiumStudentCount}), 0)::INTEGER`, + conversionPercentage: sql`COALESCE(SUM(${coursesSummaryStats.paidPurchasedAfterFreemiumCount})::DECIMAL / SUM(${coursesSummaryStats.completedFreemiumStudentCount}) * 100, 0)::INTEGER`, }) .from(coursesSummaryStats) .where(eq(coursesSummaryStats.authorId, userId)); @@ -283,8 +283,8 @@ export class StatisticsRepository { .from(studentCourses) .where( and( - gte(studentCourses.createdAt, sql`${startDate}::timestamp`), - lt(studentCourses.createdAt, sql`${endDate}::timestamp`), + gte(studentCourses.createdAt, sql`${startDate}::TIMESTAMP`), + lt(studentCourses.createdAt, sql`${endDate}::TIMESTAMP`), ), ) .groupBy(studentCourses.courseId); diff --git a/apps/api/src/storage/migrations/0015_drop_course_id_constraint_on_course_students_stats.sql b/apps/api/src/storage/migrations/0015_drop_course_id_constraint_on_course_students_stats.sql new file mode 100644 index 000000000..dc034b13d --- /dev/null +++ b/apps/api/src/storage/migrations/0015_drop_course_id_constraint_on_course_students_stats.sql @@ -0,0 +1 @@ +ALTER TABLE "course_students_stats" DROP CONSTRAINT "course_students_stats_course_id_unique"; \ No newline at end of file diff --git a/apps/api/src/storage/migrations/meta/0015_snapshot.json b/apps/api/src/storage/migrations/meta/0015_snapshot.json new file mode 100644 index 000000000..78e4bc6ff --- /dev/null +++ b/apps/api/src/storage/migrations/meta/0015_snapshot.json @@ -0,0 +1,2274 @@ +{ + "id": "dc472231-d4c5-48e0-9a65-897742965a39", + "prevId": "0bbef29e-e4a4-4b01-8b54-0c1d77cea88a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_title_unique": { + "name": "categories_title_unique", + "nullsNotDistinct": false, + "columns": [ + "title" + ] + } + } + }, + "public.conversation_messages": { + "name": "conversation_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "conversation_messages_conversation_id_conversations_id_fk": { + "name": "conversation_messages_conversation_id_conversations_id_fk", + "tableFrom": "conversation_messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "conversation_messages_author_id_users_id_fk": { + "name": "conversation_messages_author_id_users_id_fk", + "tableFrom": "conversation_messages", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "participant1_id": { + "name": "participant1_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant2_id": { + "name": "participant2_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "conversations_participant1_id_users_id_fk": { + "name": "conversations_participant1_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "participant1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "conversations_participant2_id_users_id_fk": { + "name": "conversations_participant2_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "participant2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.course_lessons": { + "name": "course_lessons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "course_lessons_course_id_courses_id_fk": { + "name": "course_lessons_course_id_courses_id_fk", + "tableFrom": "course_lessons", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "course_lessons_lesson_id_lessons_id_fk": { + "name": "course_lessons_lesson_id_lessons_id_fk", + "tableFrom": "course_lessons", + "tableTo": "lessons", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "course_lessons_course_id_lesson_id_unique": { + "name": "course_lessons_course_id_lesson_id_unique", + "nullsNotDistinct": false, + "columns": [ + "course_id", + "lesson_id" + ] + } + } + }, + "public.course_students_stats": { + "name": "course_students_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "month": { + "name": "month", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "new_students_count": { + "name": "new_students_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "course_students_stats_course_id_courses_id_fk": { + "name": "course_students_stats_course_id_courses_id_fk", + "tableFrom": "course_students_stats", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_students_stats_author_id_users_id_fk": { + "name": "course_students_stats_author_id_users_id_fk", + "tableFrom": "course_students_stats", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "course_students_stats_course_id_month_year_unique": { + "name": "course_students_stats_course_id_month_year_unique", + "nullsNotDistinct": false, + "columns": [ + "course_id", + "month", + "year" + ] + } + } + }, + "public.courses": { + "name": "courses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "price_in_cents": { + "name": "price_in_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "lessons_count": { + "name": "lessons_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "courses_author_id_users_id_fk": { + "name": "courses_author_id_users_id_fk", + "tableFrom": "courses", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "courses_category_id_categories_id_fk": { + "name": "courses_category_id_categories_id_fk", + "tableFrom": "courses", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.courses_summary_stats": { + "name": "courses_summary_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "free_purchased_count": { + "name": "free_purchased_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "paid_purchased_count": { + "name": "paid_purchased_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "paid_purchased_after_freemium_count": { + "name": "paid_purchased_after_freemium_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_freemium_student_count": { + "name": "completed_freemium_student_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_course_student_count": { + "name": "completed_course_student_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "courses_summary_stats_course_id_courses_id_fk": { + "name": "courses_summary_stats_course_id_courses_id_fk", + "tableFrom": "courses_summary_stats", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "courses_summary_stats_author_id_users_id_fk": { + "name": "courses_summary_stats_author_id_users_id_fk", + "tableFrom": "courses_summary_stats", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "courses_summary_stats_course_id_unique": { + "name": "courses_summary_stats_course_id_unique", + "nullsNotDistinct": false, + "columns": [ + "course_id" + ] + } + } + }, + "public.create_tokens": { + "name": "create_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "create_token": { + "name": "create_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiry_date": { + "name": "expiry_date", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "create_tokens_user_id_users_id_fk": { + "name": "create_tokens_user_id_users_id_fk", + "tableFrom": "create_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "title": { + "name": "title", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "files_author_id_users_id_fk": { + "name": "files_author_id_users_id_fk", + "tableFrom": "files", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.lesson_items": { + "name": "lesson_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_item_id": { + "name": "lesson_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_item_type": { + "name": "lesson_item_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "lesson_items_lesson_id_lessons_id_fk": { + "name": "lesson_items_lesson_id_lessons_id_fk", + "tableFrom": "lesson_items", + "tableTo": "lessons", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.lessons": { + "name": "lessons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "title": { + "name": "title", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'multimedia'" + }, + "items_count": { + "name": "items_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "lessons_author_id_users_id_fk": { + "name": "lessons_author_id_users_id_fk", + "tableFrom": "lessons", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "student_id": { + "name": "student_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notifications_student_id_users_id_fk": { + "name": "notifications_student_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.question_answer_options": { + "name": "question_answer_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_text": { + "name": "option_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_correct": { + "name": "is_correct", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "question_answer_options_question_id_questions_id_fk": { + "name": "question_answer_options_question_id_questions_id_fk", + "tableFrom": "question_answer_options", + "tableTo": "questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.questions": { + "name": "questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "question_type": { + "name": "question_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "question_body": { + "name": "question_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "solution_explanation": { + "name": "solution_explanation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "questions_author_id_users_id_fk": { + "name": "questions_author_id_users_id_fk", + "tableFrom": "questions", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.quiz_attempts": { + "name": "quiz_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "correct_answers": { + "name": "correct_answers", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "wrong_answers": { + "name": "wrong_answers", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "quiz_attempts_user_id_users_id_fk": { + "name": "quiz_attempts_user_id_users_id_fk", + "tableFrom": "quiz_attempts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "quiz_attempts_course_id_courses_id_fk": { + "name": "quiz_attempts_course_id_courses_id_fk", + "tableFrom": "quiz_attempts", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "quiz_attempts_lesson_id_lessons_id_fk": { + "name": "quiz_attempts_lesson_id_lessons_id_fk", + "tableFrom": "quiz_attempts", + "tableTo": "lessons", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.reset_tokens": { + "name": "reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reset_token": { + "name": "reset_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiry_date": { + "name": "expiry_date", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "reset_tokens_user_id_users_id_fk": { + "name": "reset_tokens_user_id_users_id_fk", + "tableFrom": "reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.student_completed_lesson_items": { + "name": "student_completed_lesson_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "student_id": { + "name": "student_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_item_id": { + "name": "lesson_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "student_completed_lesson_items_student_id_users_id_fk": { + "name": "student_completed_lesson_items_student_id_users_id_fk", + "tableFrom": "student_completed_lesson_items", + "tableTo": "users", + "columnsFrom": [ + "student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_completed_lesson_items_lesson_item_id_lesson_items_id_fk": { + "name": "student_completed_lesson_items_lesson_item_id_lesson_items_id_fk", + "tableFrom": "student_completed_lesson_items", + "tableTo": "lesson_items", + "columnsFrom": [ + "lesson_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_completed_lesson_items_lesson_id_lessons_id_fk": { + "name": "student_completed_lesson_items_lesson_id_lessons_id_fk", + "tableFrom": "student_completed_lesson_items", + "tableTo": "lessons", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_completed_lesson_items_course_id_courses_id_fk": { + "name": "student_completed_lesson_items_course_id_courses_id_fk", + "tableFrom": "student_completed_lesson_items", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "student_completed_lesson_items_student_id_lesson_item_id_lesson_id_course_id_unique": { + "name": "student_completed_lesson_items_student_id_lesson_item_id_lesson_id_course_id_unique", + "nullsNotDistinct": false, + "columns": [ + "student_id", + "lesson_item_id", + "lesson_id", + "course_id" + ] + } + } + }, + "public.student_courses": { + "name": "student_courses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "student_id": { + "name": "student_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "finished_lessons_count": { + "name": "finished_lessons_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_started'" + }, + "payment_id": { + "name": "payment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "student_courses_student_id_users_id_fk": { + "name": "student_courses_student_id_users_id_fk", + "tableFrom": "student_courses", + "tableTo": "users", + "columnsFrom": [ + "student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_courses_course_id_courses_id_fk": { + "name": "student_courses_course_id_courses_id_fk", + "tableFrom": "student_courses", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "student_courses_student_id_course_id_unique": { + "name": "student_courses_student_id_course_id_unique", + "nullsNotDistinct": false, + "columns": [ + "student_id", + "course_id" + ] + } + } + }, + "public.student_favourited_courses": { + "name": "student_favourited_courses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "student_id": { + "name": "student_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "student_favourited_courses_student_id_users_id_fk": { + "name": "student_favourited_courses_student_id_users_id_fk", + "tableFrom": "student_favourited_courses", + "tableTo": "users", + "columnsFrom": [ + "student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_favourited_courses_course_id_courses_id_fk": { + "name": "student_favourited_courses_course_id_courses_id_fk", + "tableFrom": "student_favourited_courses", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "student_favourited_courses_student_id_course_id_unique": { + "name": "student_favourited_courses_student_id_course_id_unique", + "nullsNotDistinct": false, + "columns": [ + "student_id", + "course_id" + ] + } + } + }, + "public.student_lessons_progress": { + "name": "student_lessons_progress", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "student_id": { + "name": "student_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "completed_lesson_item_count": { + "name": "completed_lesson_item_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "quiz_completed": { + "name": "quiz_completed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "quiz_score": { + "name": "quiz_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_as_freemium": { + "name": "completed_as_freemium", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "student_lessons_progress_student_id_users_id_fk": { + "name": "student_lessons_progress_student_id_users_id_fk", + "tableFrom": "student_lessons_progress", + "tableTo": "users", + "columnsFrom": [ + "student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_lessons_progress_course_id_courses_id_fk": { + "name": "student_lessons_progress_course_id_courses_id_fk", + "tableFrom": "student_lessons_progress", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_lessons_progress_lesson_id_lessons_id_fk": { + "name": "student_lessons_progress_lesson_id_lessons_id_fk", + "tableFrom": "student_lessons_progress", + "tableTo": "lessons", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "student_lessons_progress_student_id_lesson_id_course_id_unique": { + "name": "student_lessons_progress_student_id_lesson_id_course_id_unique", + "nullsNotDistinct": false, + "columns": [ + "student_id", + "lesson_id", + "course_id" + ] + } + } + }, + "public.student_question_answers": { + "name": "student_question_answers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "student_id": { + "name": "student_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "is_correct": { + "name": "is_correct", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "student_question_answers_course_id_courses_id_fk": { + "name": "student_question_answers_course_id_courses_id_fk", + "tableFrom": "student_question_answers", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_question_answers_lesson_id_lessons_id_fk": { + "name": "student_question_answers_lesson_id_lessons_id_fk", + "tableFrom": "student_question_answers", + "tableTo": "lessons", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_question_answers_question_id_questions_id_fk": { + "name": "student_question_answers_question_id_questions_id_fk", + "tableFrom": "student_question_answers", + "tableTo": "questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "student_question_answers_student_id_users_id_fk": { + "name": "student_question_answers_student_id_users_id_fk", + "tableFrom": "student_question_answers", + "tableTo": "users", + "columnsFrom": [ + "student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "student_question_answers_lesson_id_question_id_student_id_unique": { + "name": "student_question_answers_lesson_id_question_id_student_id_unique", + "nullsNotDistinct": false, + "columns": [ + "lesson_id", + "question_id", + "student_id" + ] + } + } + }, + "public.text_blocks": { + "name": "text_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "title": { + "name": "title", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "text_blocks_author_id_users_id_fk": { + "name": "text_blocks_author_id_users_id_fk", + "tableFrom": "text_blocks", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user_details": { + "name": "user_details", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "contact_phone_number": { + "name": "contact_phone_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contact_email": { + "name": "contact_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_details_user_id_users_id_fk": { + "name": "user_details_user_id_users_id_fk", + "tableFrom": "user_details", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_details_user_id_unique": { + "name": "user_details_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_statistics": { + "name": "user_statistics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "current_streak": { + "name": "current_streak", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "longest_streak": { + "name": "longest_streak", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_activity_date": { + "name": "last_activity_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "activity_history": { + "name": "activity_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_statistics_user_id_users_id_fk": { + "name": "user_statistics_user_id_users_id_fk", + "tableFrom": "user_statistics", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_statistics_user_id_unique": { + "name": "user_statistics_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'student'" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/api/src/storage/migrations/meta/_journal.json b/apps/api/src/storage/migrations/meta/_journal.json index 7e4f7a9f1..bf8c19073 100644 --- a/apps/api/src/storage/migrations/meta/_journal.json +++ b/apps/api/src/storage/migrations/meta/_journal.json @@ -106,6 +106,13 @@ "when": 1733102008225, "tag": "0014_create_course_students_stats_table", "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1733151093964, + "tag": "0015_drop_course_id_constraint_on_course_students_stats", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/api/src/storage/schema/index.ts b/apps/api/src/storage/schema/index.ts index 13c224c9f..9ab3c7318 100644 --- a/apps/api/src/storage/schema/index.ts +++ b/apps/api/src/storage/schema/index.ts @@ -404,8 +404,6 @@ export const coursesSummaryStats = pgTable("courses_summary_stats", { .default(0), completedFreemiumStudentCount: integer("completed_freemium_student_count").notNull().default(0), completedCourseStudentCount: integer("completed_course_student_count").notNull().default(0), - correctAnswersCount: integer("correct_answers_count").notNull().default(0), - wrongAnswersCount: integer("wrong_answers_count").notNull().default(0), }); export const courseStudentsStats = pgTable( @@ -415,7 +413,6 @@ export const courseStudentsStats = pgTable( ...timestamps, courseId: uuid("course_id") .references(() => courses.id, { onDelete: "cascade" }) - .unique() .notNull(), authorId: uuid("author_id") .references(() => users.id, { onDelete: "cascade" }) diff --git a/apps/web/app/api/generated-api.ts b/apps/web/app/api/generated-api.ts index b5bf82763..68dc7546e 100644 --- a/apps/web/app/api/generated-api.ts +++ b/apps/web/app/api/generated-api.ts @@ -1210,9 +1210,9 @@ export interface GetTeacherStatsResponse { totalCourses: number; }; conversionAfterFreemiumLesson: { + conversionPercentage: number; purchasedCourses: number; remainedOnFreemium: number; - conversionPercentage: number; }; courseStudentsStats: object; avgQuizScore: {