Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add teacher stats to seed #271

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 89 additions & 6 deletions apps/api/src/seed.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -16,12 +16,15 @@ import {
categories,
courseLessons,
courses,
coursesSummaryStats,
courseStudentsStats,
credentials,
files,
lessonItems,
lessons,
questionAnswerOptions,
questions,
quizAttempts,
studentCourses,
studentLessonsProgress,
textBlocks,
Expand Down Expand Up @@ -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);

Expand All @@ -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(),
Expand All @@ -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");
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/statistics/repositories/statistics.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export class StatisticsRepository {
.select({
totalCoursesCompletion: sql<number>`COALESCE(SUM(${coursesSummaryStats.completedCourseStudentCount}), 0)::INTEGER`,
totalCourses: sql<number>`COALESCE(SUM(${coursesSummaryStats.freePurchasedCount} + ${coursesSummaryStats.paidPurchasedCount}), 0)::INTEGER`,
completionPercentage: sql<number>`COALESCE(SUM(${coursesSummaryStats.completedCourseStudentCount}) / SUM(${coursesSummaryStats.freePurchasedCount} + ${coursesSummaryStats.paidPurchasedCount}), 0)::INTEGER`,
completionPercentage: sql<number>`(SUM(${coursesSummaryStats.completedCourseStudentCount})::DECIMAL / (SUM(${coursesSummaryStats.freePurchasedCount} + ${coursesSummaryStats.paidPurchasedCount})) * 100)::INTEGER`,
})
.from(coursesSummaryStats)
.where(eq(coursesSummaryStats.authorId, userId));
Expand All @@ -167,7 +167,7 @@ export class StatisticsRepository {
.select({
purchasedCourses: sql<number>`COALESCE(SUM(${coursesSummaryStats.paidPurchasedAfterFreemiumCount}), 0)::INTEGER`,
remainedOnFreemium: sql<number>`COALESCE(SUM(${coursesSummaryStats.completedFreemiumStudentCount} - ${coursesSummaryStats.paidPurchasedAfterFreemiumCount}), 0)::INTEGER`,
conversionPercentage: sql<number>`COALESCE(SUM(${coursesSummaryStats.paidPurchasedAfterFreemiumCount}) / SUM(${coursesSummaryStats.completedFreemiumStudentCount}), 0)::INTEGER`,
conversionPercentage: sql<number>`COALESCE(SUM(${coursesSummaryStats.paidPurchasedAfterFreemiumCount})::DECIMAL / SUM(${coursesSummaryStats.completedFreemiumStudentCount}) * 100, 0)::INTEGER`,
})
.from(coursesSummaryStats)
.where(eq(coursesSummaryStats.authorId, userId));
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "course_students_stats" DROP CONSTRAINT "course_students_stats_course_id_unique";
Loading