diff --git a/apps/api/package.json b/apps/api/package.json index ebf7cdd85..e6d54a528 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -55,6 +55,7 @@ "bcrypt": "^5.1.1", "cookie": "^0.6.0", "cookie-parser": "^1.4.6", + "date-fns": "^3.6.0", "dotenv": "^16.4.5", "drizzle-kit": "^0.22.8", "drizzle-orm": "^0.31.2", diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index eb380ed42..c5ccab1d7 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -19,10 +19,12 @@ import { EmailModule } from "./common/emails/emails.module"; import { JwtAuthGuard } from "./common/guards/jwt-auth.guard"; import { StagingGuard } from "./common/guards/staging.guard"; import { CoursesModule } from "./courses/courses.module"; +import { EventsModule } from "./events/events.module"; import { S3Module } from "./file/s3.module"; import { HealthModule } from "./health/health.module"; import { LessonsModule } from "./lessons/lessons.module"; import { QuestionsModule } from "./questions/questions.module"; +import { StatisticsModule } from "./statistics/statistics.module"; import * as schema from "./storage/schema"; import { StripeModule } from "./stripe/stripe.module"; import { StudentCompletedLessonItemsModule } from "./studentCompletedLessonItem/studentCompletedLessonItems.module"; @@ -73,6 +75,8 @@ import { TestConfigModule } from "./test-config/test-config.module"; StudentCompletedLessonItemsModule, S3Module, StripeModule, + EventsModule, + StatisticsModule, ], controllers: [], providers: [ diff --git a/apps/api/src/auth/api/auth.controller.ts b/apps/api/src/auth/api/auth.controller.ts index 8865b78f1..b45d701d9 100644 --- a/apps/api/src/auth/api/auth.controller.ts +++ b/apps/api/src/auth/api/auth.controller.ts @@ -8,6 +8,7 @@ import { UnauthorizedException, UseGuards, } from "@nestjs/common"; +import { EventBus } from "@nestjs/cqrs"; import { AuthGuard } from "@nestjs/passport"; import { type Request, Response } from "express"; import { Validate } from "nestjs-typebox"; @@ -18,6 +19,7 @@ import { Roles } from "src/common/decorators/roles.decorator"; import { CurrentUser } from "src/common/decorators/user.decorator"; import { RefreshTokenGuard } from "src/common/guards/refresh-token.guard"; import { commonUserSchema } from "src/common/schemas/common-user.schema"; +import { UserActivityEvent } from "src/events"; import { USER_ROLES } from "src/users/schemas/user-roles"; import { AuthService } from "../auth.service"; @@ -39,6 +41,7 @@ export class AuthController { constructor( private readonly authService: AuthService, private readonly tokenService: TokenService, + private readonly eventBus: EventBus, ) {} @Public() @@ -115,6 +118,8 @@ export class AuthController { ): Promise>> { const account = await this.authService.currentUser(currentUserId); + this.eventBus.publish(new UserActivityEvent(currentUserId, "LOGIN")); + return new BaseResponse(account); } diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 50896e938..a9c8ff927 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -1,6 +1,7 @@ import { BadRequestException, ConflictException, + ForbiddenException, Inject, Injectable, NotFoundException, @@ -120,7 +121,7 @@ export class AuthService { const tokens = await this.getTokens(user); return tokens; } catch (error) { - throw new UnauthorizedException("Invalid refresh token"); + throw new ForbiddenException("Invalid refresh token"); } } diff --git a/apps/api/src/common/guards/refresh-token.guard.ts b/apps/api/src/common/guards/refresh-token.guard.ts index f4597ccc0..8aaced49a 100644 --- a/apps/api/src/common/guards/refresh-token.guard.ts +++ b/apps/api/src/common/guards/refresh-token.guard.ts @@ -3,6 +3,7 @@ import { type CanActivate, type ExecutionContext, UnauthorizedException, + ForbiddenException, } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { JwtService } from "@nestjs/jwt"; @@ -34,7 +35,7 @@ export class RefreshTokenGuard implements CanActivate { return true; } catch { - throw new UnauthorizedException("Invalid refresh token"); + throw new ForbiddenException("Invalid refresh token"); } } } diff --git a/apps/api/src/common/types.ts b/apps/api/src/common/types.ts new file mode 100644 index 000000000..d12c61beb --- /dev/null +++ b/apps/api/src/common/types.ts @@ -0,0 +1,3 @@ +export type ActivityHistory = { + [date: string]: boolean; +}; diff --git a/apps/api/src/events/course/course-activity.event.ts b/apps/api/src/events/course/course-activity.event.ts new file mode 100644 index 000000000..2260597d4 --- /dev/null +++ b/apps/api/src/events/course/course-activity.event.ts @@ -0,0 +1,13 @@ +export class CourseStartedEvent { + constructor( + public readonly userId: string, + public readonly courseId: string, + ) {} +} + +export class CourseCompletedEvent { + constructor( + public readonly userId: string, + public readonly courseId: string, + ) {} +} diff --git a/apps/api/src/events/events.module.ts b/apps/api/src/events/events.module.ts new file mode 100644 index 000000000..15b8fcea3 --- /dev/null +++ b/apps/api/src/events/events.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from "@nestjs/common"; +import { CqrsModule } from "@nestjs/cqrs"; + +@Global() +@Module({ + imports: [CqrsModule], + exports: [CqrsModule], +}) +export class EventsModule {} diff --git a/apps/api/src/events/index.ts b/apps/api/src/events/index.ts new file mode 100644 index 000000000..8c5f7ae25 --- /dev/null +++ b/apps/api/src/events/index.ts @@ -0,0 +1,4 @@ +export * from "./course/course-activity.event"; +export * from "./lesson/lesson-completed.event"; +export * from "./quiz/quiz-completed.event"; +export * from "./user/user-activity.event"; diff --git a/apps/api/src/events/lesson/lesson-completed.event.ts b/apps/api/src/events/lesson/lesson-completed.event.ts new file mode 100644 index 000000000..23ee713cd --- /dev/null +++ b/apps/api/src/events/lesson/lesson-completed.event.ts @@ -0,0 +1,7 @@ +export class LessonCompletedEvent { + constructor( + public readonly userId: string, + public readonly courseId: string, + public readonly lessonId: string, + ) {} +} diff --git a/apps/api/src/events/quiz/quiz-completed.event.ts b/apps/api/src/events/quiz/quiz-completed.event.ts new file mode 100644 index 000000000..ef6445ad8 --- /dev/null +++ b/apps/api/src/events/quiz/quiz-completed.event.ts @@ -0,0 +1,10 @@ +export class QuizCompletedEvent { + constructor( + public readonly userId: string, + public readonly courseId: string, + public readonly lessonId: string, + public readonly correctAnswers: number, + public readonly wrongAnswers: number, + public readonly score: number, + ) {} +} diff --git a/apps/api/src/events/user/user-activity.event.ts b/apps/api/src/events/user/user-activity.event.ts new file mode 100644 index 000000000..1014fa1d3 --- /dev/null +++ b/apps/api/src/events/user/user-activity.event.ts @@ -0,0 +1,9 @@ +export type UserActivityType = "LOGIN" | "LESSON_PROGRESS" | "COURSE_PROGRESS"; + +export class UserActivityEvent { + constructor( + public readonly userId: string, + public readonly activityType: UserActivityType, + public readonly metadata?: Record, + ) {} +} diff --git a/apps/api/src/lessons/lessons.service.ts b/apps/api/src/lessons/lessons.service.ts index 74750c123..58bd73d78 100644 --- a/apps/api/src/lessons/lessons.service.ts +++ b/apps/api/src/lessons/lessons.service.ts @@ -5,10 +5,12 @@ import { NotFoundException, UnauthorizedException, } from "@nestjs/common"; +import { EventBus } from "@nestjs/cqrs"; import { isNull } from "lodash"; import { match, P } from "ts-pattern"; import { DatabasePg } from "src/common"; +import { QuizCompletedEvent } from "src/events"; import { S3Service } from "src/file/s3.service"; import { LessonProgress } from "src/lessons/schemas/lesson.types"; @@ -30,6 +32,7 @@ export class LessonsService { @Inject("DB") private readonly db: DatabasePg, private readonly s3Service: S3Service, private readonly lessonsRepository: LessonsRepository, + private readonly eventBus: EventBus, ) {} async getLesson(id: UUIDType, courseId: UUIDType, userId: UUIDType, isAdmin?: boolean) { @@ -163,6 +166,10 @@ export class LessonsService { userId, true, ); + + let correctAnswers = 0; + let wrongAnswers = 0; + try { await this.db.transaction(async (trx) => { await Promise.all( @@ -228,9 +235,23 @@ export class LessonsService { passQuestion, trx, ); + + if (passQuestion) { + correctAnswers++; + } else { + wrongAnswers++; + } }), ); }); + + const totalQuestions = questionLessonItems.length; + const score = Math.round((correctAnswers / totalQuestions) * 100); + + this.eventBus.publish( + new QuizCompletedEvent(userId, courseId, lessonId, correctAnswers, wrongAnswers, score), + ); + return true; } catch (error) { console.log("error", error); diff --git a/apps/api/src/statistics/api/statistics.controller.ts b/apps/api/src/statistics/api/statistics.controller.ts new file mode 100644 index 000000000..26c0159ad --- /dev/null +++ b/apps/api/src/statistics/api/statistics.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get } from "@nestjs/common"; +import { Validate } from "nestjs-typebox"; + +import { baseResponse, BaseResponse, UUIDType } from "src/common"; + +import { CurrentUser } from "../../common/decorators/user.decorator"; +import { UserStatsSchema } from "../schemas/userStats.schema"; +import { StatisticsService } from "../statistics.service"; + +import type { UserStats } from "../schemas/userStats.schema"; + +@Controller("statistics") +export class StatisticsController { + constructor(private statisticsService: StatisticsService) {} + + @Get() + @Validate({ + response: baseResponse(UserStatsSchema), + }) + async getUserStatistics( + @CurrentUser("userId") currentUserId: UUIDType, + ): Promise> { + return new BaseResponse(await this.statisticsService.getUserStats(currentUserId)); + } +} diff --git a/apps/api/src/statistics/handlers/statistics.handler.ts b/apps/api/src/statistics/handlers/statistics.handler.ts new file mode 100644 index 000000000..07805b94a --- /dev/null +++ b/apps/api/src/statistics/handlers/statistics.handler.ts @@ -0,0 +1,55 @@ +import { Injectable } from "@nestjs/common"; +import { EventsHandler } from "@nestjs/cqrs"; +import { match } from "ts-pattern"; + +import { QuizCompletedEvent, UserActivityEvent, CourseStartedEvent } from "src/events"; + +import { StatisticsRepository } from "../repositories/statistics.repository"; + +import type { IEventHandler } from "@nestjs/cqrs"; + +type StatisticsEvent = QuizCompletedEvent | UserActivityEvent | CourseStartedEvent; + +@Injectable() +@EventsHandler(QuizCompletedEvent, UserActivityEvent, CourseStartedEvent) +export class StatisticsHandler implements IEventHandler { + constructor(private readonly statisticsRepository: StatisticsRepository) {} + + async handle(event: StatisticsEvent) { + try { + match(event) + .when( + (e): e is QuizCompletedEvent => e instanceof QuizCompletedEvent, + async (quizEvent) => { + await this.handleQuizCompleted(quizEvent); + }, + ) + .when( + (e): e is UserActivityEvent => e instanceof UserActivityEvent, + async (activityEvent) => { + await this.handleUserActivity(activityEvent); + }, + ) + .otherwise(() => { + throw new Error("Unknown event type"); + }); + } catch (error) { + console.error("Error handling event:", error); + } + } + + private async handleQuizCompleted(event: QuizCompletedEvent) { + await this.statisticsRepository.createQuizAttempt({ + userId: event.userId, + courseId: event.courseId, + lessonId: event.lessonId, + correctAnswers: event.correctAnswers, + wrongAnswers: event.wrongAnswers, + score: event.score, + }); + } + + private async handleUserActivity(event: UserActivityEvent) { + await this.statisticsRepository.updateUserActivity(event.userId); + } +} diff --git a/apps/api/src/statistics/repositories/statistics.repository.ts b/apps/api/src/statistics/repositories/statistics.repository.ts new file mode 100644 index 000000000..f50bbc14b --- /dev/null +++ b/apps/api/src/statistics/repositories/statistics.repository.ts @@ -0,0 +1,261 @@ +import { Inject, Injectable } from "@nestjs/common"; +import { startOfDay, differenceInDays, eachDayOfInterval, format } from "date-fns"; +import { and, eq, sql } from "drizzle-orm"; + +import { DatabasePg } from "src/common"; + +import { + quizAttempts, + studentCourses, + studentLessonsProgress, + userStatistics, +} from "../../storage/schema"; + +type Stats = { + month: string; + started: number; + completed: number; + completionRate: number; +}; + +@Injectable() +export class StatisticsRepository { + constructor(@Inject("DB") private readonly db: DatabasePg) {} + + async getUserStats(userId: string) { + const [quizStatsResult] = await this.db + .select({ + totalAttempts: sql`count(*)`, + totalCorrectAnswers: sql`coalesce(sum(${quizAttempts.correctAnswers}), 0)`, + totalWrongAnswers: sql`coalesce(sum(${quizAttempts.wrongAnswers}), 0)`, + totalQuestions: sql`coalesce(sum(${quizAttempts.correctAnswers} + ${quizAttempts.wrongAnswers}), 0)`, + averageScore: sql`coalesce(round(avg(${quizAttempts.score}), 2), 0)`, + uniqueQuizzesTaken: sql`count(distinct ${quizAttempts.lessonId})`, + }) + .from(quizAttempts) + .where(eq(quizAttempts.userId, userId)) + .groupBy(quizAttempts.userId); + + const quizStats = quizStatsResult || { + totalAttempts: 0, + totalCorrectAnswers: 0, + totalWrongAnswers: 0, + totalQuestions: 0, + averageScore: 0, + uniqueQuizzesTaken: 0, + }; + + const courseStatsResult = await this.db + .select({ + month: sql`to_char(date_trunc('month', ${studentCourses.createdAt}), 'YYYY-MM')`, + started: sql`count(*)`, + completed: sql`count(case when ${studentCourses.state} = 'completed' then 1 end)`, + completionRate: sql` + coalesce( + round( + (count(case when ${studentCourses.state} = 'completed' then 1 end)::numeric / + nullif(count(*)::numeric, 0)) * 100, + 2 + ), + 0 + ) + `, + }) + .from(studentCourses) + .where( + and( + eq(studentCourses.studentId, userId), + sql`${studentCourses.createdAt} >= date_trunc('month', current_date) - interval '11 months'`, + ), + ) + .groupBy(sql`date_trunc('month', ${studentCourses.createdAt})`) + .orderBy(sql`date_trunc('month', ${studentCourses.createdAt})`); + + const [activityStats] = await this.db + .select() + .from(userStatistics) + .where(eq(userStatistics.userId, userId)); + + const lessonStatsResult = await this.db + .select({ + month: sql`to_char(date_trunc('month', ${studentLessonsProgress.createdAt}), 'YYYY-MM')`, + started: sql`count(distinct ${studentLessonsProgress.lessonId})`, + completed: sql`count(case when ${studentLessonsProgress.completedLessonItemCount} = ${studentLessonsProgress.lessonItemCount} then 1 end)`, + completionRate: sql` + coalesce( + round( + (count(case when ${studentLessonsProgress.completedLessonItemCount} = ${studentLessonsProgress.lessonItemCount} then 1 end)::numeric / + nullif(count(distinct ${studentLessonsProgress.lessonId})::numeric, 0)) * 100, + 2 + ), + 0 + ) + `, + }) + .from(studentLessonsProgress) + .where( + and( + eq(studentLessonsProgress.studentId, userId), + sql`${studentLessonsProgress.createdAt} >= date_trunc('month', current_date) - interval '11 months'`, + ), + ) + + .groupBy(sql`date_trunc('month', ${studentLessonsProgress.createdAt})`) + .orderBy(sql`date_trunc('month', ${studentLessonsProgress.createdAt})`); + + return { + quizzes: { + totalAttempts: Number(quizStats.totalAttempts), + totalCorrectAnswers: Number(quizStats.totalCorrectAnswers), + totalWrongAnswers: Number(quizStats.totalWrongAnswers), + totalQuestions: Number(quizStats.totalQuestions), + averageScore: Number(quizStats.averageScore), + uniqueQuizzesTaken: Number(quizStats.uniqueQuizzesTaken), + }, + courses: this.formatCourseStats(courseStatsResult), + streak: { + current: Number(activityStats?.currentStreak) || 0, + longest: Number(activityStats?.longestStreak) || 0, + activityHistory: activityStats?.activityHistory || {}, + }, + lessons: this.formatLessonStats(lessonStatsResult), + }; + } + + async createQuizAttempt(data: { + userId: string; + courseId: string; + lessonId: string; + correctAnswers: number; + wrongAnswers: number; + score: number; + }) { + return await this.db.insert(quizAttempts).values(data); + } + + async updateUserActivity(userId: string) { + const today = startOfDay(new Date()); + const formatedTodayDate = format(today, "yyyy-MM-dd"); + + const [currentStats] = await this.db + .select({ + currentStreak: userStatistics.currentStreak, + longestStreak: userStatistics.longestStreak, + lastActivityDate: userStatistics.lastActivityDate, + activityHistory: userStatistics.activityHistory, + }) + .from(userStatistics) + .where(eq(userStatistics.userId, userId)); + + const lastActivityDate = currentStats?.lastActivityDate + ? startOfDay(new Date(currentStats.lastActivityDate)) + : null; + + const newCurrentStreak = (() => { + if (!lastActivityDate) return 1; + + const daysDiff = differenceInDays(today, lastActivityDate); + + if (daysDiff === 0) return currentStats?.currentStreak ?? 1; + if (daysDiff === 1) return (currentStats?.currentStreak ?? 0) + 1; + return 1; + })(); + + const newLongestStreak = Math.max(newCurrentStreak, currentStats?.longestStreak ?? 0); + + const isUserLoggedInToday = currentStats?.activityHistory?.[formatedTodayDate] ?? false; + if (isUserLoggedInToday) { + return; + } + + const dateRange = lastActivityDate + ? eachDayOfInterval({ start: lastActivityDate, end: today }) + : [today]; + + const newActivityHistory = dateRange.reduce( + (acc, date) => { + const dateStr = format(date, "yyyy-MM-dd"); + if (!acc[dateStr]) { + acc[dateStr] = dateStr === formatedTodayDate; + } + return acc; + }, + { ...(currentStats?.activityHistory ?? {}) }, + ); + + await this.db + .insert(userStatistics) + .values({ + userId, + currentStreak: newCurrentStreak, + longestStreak: newLongestStreak, + lastActivityDate: today, + activityHistory: newActivityHistory, + }) + .onConflictDoUpdate({ + target: userStatistics.userId, + set: { + currentStreak: newCurrentStreak, + longestStreak: newLongestStreak, + lastActivityDate: today, + activityHistory: newActivityHistory, + }, + }); + } + + private formatCourseStats = (courseStats: Stats[]) => { + const monthlyStats: { [key: string]: Omit } = {}; + + const currentDate = new Date(); + for (let index = 11; index >= 0; index--) { + const month = format( + new Date(currentDate.getFullYear(), currentDate.getMonth() - index, 1), + "MMMM", + ); + monthlyStats[month] = { + started: 0, + completed: 0, + completionRate: 0, + }; + } + + for (const stat of courseStats) { + const month = format(new Date(stat.month), "MMMM"); + monthlyStats[month] = { + started: Number(stat.started), + completed: Number(stat.completed), + completionRate: Number(stat.completionRate), + }; + } + + return monthlyStats; + }; + + private formatLessonStats = (lessonStats: Stats[]) => { + const monthlyStats: { [key: string]: Omit } = {}; + + const currentDate = new Date(); + for (let index = 11; index >= 0; index--) { + const month = format( + new Date(currentDate.getFullYear(), currentDate.getMonth() - index, 1), + "MMMM", + ); + monthlyStats[month] = { + started: 0, + completed: 0, + completionRate: 0, + }; + } + + for (const stat of lessonStats) { + const month = format(new Date(stat.month), "MMMM"); + monthlyStats[month] = { + started: Number(stat.started), + completed: Number(stat.completed), + completionRate: Number(stat.completionRate), + }; + } + + return monthlyStats; + }; +} diff --git a/apps/api/src/statistics/schemas/userStats.schema.ts b/apps/api/src/statistics/schemas/userStats.schema.ts new file mode 100644 index 000000000..61c1e5d7e --- /dev/null +++ b/apps/api/src/statistics/schemas/userStats.schema.ts @@ -0,0 +1,39 @@ +import { Type } from "@sinclair/typebox"; + +import type { Static } from "@sinclair/typebox"; + +export const ActivityHistorySchema = Type.Record(Type.String(), Type.Boolean()); + +export const QuizStatsSchema = Type.Object({ + totalAttempts: Type.Number(), + totalCorrectAnswers: Type.Number(), + totalWrongAnswers: Type.Number(), + totalQuestions: Type.Number(), + averageScore: Type.Number(), + uniqueQuizzesTaken: Type.Number(), +}); + +const MonthlyStatsSchema = Type.Object({ + started: Type.Number(), + completed: Type.Number(), + completionRate: Type.Number(), +}); + +export const CourseStatsSchema = Type.Record(Type.String(), MonthlyStatsSchema); + +export const LessonsStatsSchema = Type.Record(Type.String(), MonthlyStatsSchema); + +export const StreakSchema = Type.Object({ + current: Type.Number(), + longest: Type.Number(), + activityHistory: ActivityHistorySchema, +}); + +export const UserStatsSchema = Type.Object({ + quizzes: QuizStatsSchema, + courses: CourseStatsSchema, + lessons: LessonsStatsSchema, + streak: StreakSchema, +}); + +export type UserStats = Static; diff --git a/apps/api/src/statistics/statistics.module.ts b/apps/api/src/statistics/statistics.module.ts new file mode 100644 index 000000000..de2ab9c03 --- /dev/null +++ b/apps/api/src/statistics/statistics.module.ts @@ -0,0 +1,15 @@ +import { Module } from "@nestjs/common"; +import { CqrsModule } from "@nestjs/cqrs"; + +import { StatisticsController } from "./api/statistics.controller"; +import { StatisticsHandler } from "./handlers/statistics.handler"; +import { StatisticsRepository } from "./repositories/statistics.repository"; +import { StatisticsService } from "./statistics.service"; + +@Module({ + imports: [CqrsModule], + controllers: [StatisticsController], + providers: [StatisticsHandler, StatisticsRepository, StatisticsService], + exports: [StatisticsRepository], +}) +export class StatisticsModule {} diff --git a/apps/api/src/statistics/statistics.service.ts b/apps/api/src/statistics/statistics.service.ts new file mode 100644 index 000000000..5321fdabb --- /dev/null +++ b/apps/api/src/statistics/statistics.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; + +import { StatisticsRepository } from "./repositories/statistics.repository"; + +@Injectable() +export class StatisticsService { + constructor(private statisticsRepository: StatisticsRepository) {} + + async getUserStats(userId: string) { + return this.statisticsRepository.getUserStats(userId); + } +} diff --git a/apps/api/src/storage/migrations/0009_create_statistics.sql b/apps/api/src/storage/migrations/0009_create_statistics.sql new file mode 100644 index 000000000..48d54da82 --- /dev/null +++ b/apps/api/src/storage/migrations/0009_create_statistics.sql @@ -0,0 +1,47 @@ +CREATE TABLE IF NOT EXISTS "quiz_attempts" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "user_id" uuid NOT NULL, + "course_id" uuid NOT NULL, + "lesson_id" uuid NOT NULL, + "correct_answers" integer NOT NULL, + "wrong_answers" integer NOT NULL, + "score" integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user_statistics" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "user_id" uuid NOT NULL, + "current_streak" integer DEFAULT 0 NOT NULL, + "longest_streak" integer DEFAULT 0 NOT NULL, + "last_activity_date" timestamp with time zone, + "activity_history" jsonb DEFAULT '{}'::jsonb, + CONSTRAINT "user_statistics_user_id_unique" UNIQUE("user_id") +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "quiz_attempts" ADD CONSTRAINT "quiz_attempts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "quiz_attempts" ADD CONSTRAINT "quiz_attempts_course_id_courses_id_fk" FOREIGN KEY ("course_id") REFERENCES "public"."courses"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "quiz_attempts" ADD CONSTRAINT "quiz_attempts_lesson_id_lessons_id_fk" FOREIGN KEY ("lesson_id") REFERENCES "public"."lessons"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "user_statistics" ADD CONSTRAINT "user_statistics_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/apps/api/src/storage/migrations/meta/0009_snapshot.json b/apps/api/src/storage/migrations/meta/0009_snapshot.json new file mode 100644 index 000000000..245cdeb35 --- /dev/null +++ b/apps/api/src/storage/migrations/meta/0009_snapshot.json @@ -0,0 +1,2041 @@ +{ + "id": "fd627228-063a-40d1-8b82-f903ebaf5a94", + "prevId": "8ee0e8ee-f2e2-4e25-8888-54b34958af06", + "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.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 + }, + "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.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'" + } + }, + "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 + }, + "number_of_assignments": { + "name": "number_of_assignments", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "number_of_finished_assignments": { + "name": "number_of_finished_assignments", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_started'" + }, + "payment_id": { + "name": "payment_id", + "type": "text", + "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 + }, + "quiz_completed": { + "name": "quiz_completed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "lesson_item_count": { + "name": "lesson_item_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "completed_lesson_item_count": { + "name": "completed_lesson_item_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "quiz_score": { + "name": "quiz_score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "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 9c9763882..41ccfc3ad 100644 --- a/apps/api/src/storage/migrations/meta/_journal.json +++ b/apps/api/src/storage/migrations/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1731575473118, "tag": "0008_add_is_free_to_course_lessons", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1731976176840, + "tag": "0009_create_statistics", + "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 38423d2ab..32165a4f4 100644 --- a/apps/api/src/storage/schema/index.ts +++ b/apps/api/src/storage/schema/index.ts @@ -14,6 +14,8 @@ import { USER_ROLES } from "src/users/schemas/user-roles"; import { archived, id, timestamps } from "./utils"; +import type { ActivityHistory } from "src/common/types"; + export const users = pgTable("users", { ...id, ...timestamps, @@ -37,6 +39,38 @@ export const userDetails = pgTable("user_details", { jobTitle: text("job_title"), }); +export const userStatistics = pgTable("user_statistics", { + ...id, + ...timestamps, + userId: uuid("user_id") + .references(() => users.id, { onDelete: "cascade" }) + .notNull() + .unique(), + + currentStreak: integer("current_streak").notNull().default(0), + longestStreak: integer("longest_streak").notNull().default(0), + lastActivityDate: timestamp("last_activity_date", { withTimezone: true }), + + activityHistory: jsonb("activity_history").$type().default({}), +}); + +export const quizAttempts = pgTable("quiz_attempts", { + ...id, + ...timestamps, + userId: uuid("user_id") + .references(() => users.id) + .notNull(), + courseId: uuid("course_id") + .references(() => courses.id) + .notNull(), + lessonId: uuid("lesson_id") + .references(() => lessons.id) + .notNull(), + correctAnswers: integer("correct_answers").notNull(), + wrongAnswers: integer("wrong_answers").notNull(), + score: integer("score").notNull(), +}); + export const credentials = pgTable("credentials", { ...id, ...timestamps, diff --git a/apps/api/src/swagger/api-schema.json b/apps/api/src/swagger/api-schema.json index c9401ca90..7c4cb0dba 100644 --- a/apps/api/src/swagger/api-schema.json +++ b/apps/api/src/swagger/api-schema.json @@ -2581,6 +2581,23 @@ } } } + }, + "/api/statistics": { + "get": { + "operationId": "StatisticsController_getUserStatistics", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetUserStatisticsResponse" + } + } + } + } + } + } } }, "info": { @@ -6736,6 +6753,128 @@ "required": [ "data" ] + }, + "GetUserStatisticsResponse": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "quizzes": { + "type": "object", + "properties": { + "totalAttempts": { + "type": "number" + }, + "totalCorrectAnswers": { + "type": "number" + }, + "totalWrongAnswers": { + "type": "number" + }, + "totalQuestions": { + "type": "number" + }, + "averageScore": { + "type": "number" + }, + "uniqueQuizzesTaken": { + "type": "number" + } + }, + "required": [ + "totalAttempts", + "totalCorrectAnswers", + "totalWrongAnswers", + "totalQuestions", + "averageScore", + "uniqueQuizzesTaken" + ] + }, + "courses": { + "type": "object", + "patternProperties": { + "^(.*)$": { + "type": "object", + "properties": { + "started": { + "type": "number" + }, + "completed": { + "type": "number" + }, + "completionRate": { + "type": "number" + } + }, + "required": [ + "started", + "completed", + "completionRate" + ] + } + } + }, + "lessons": { + "type": "object", + "patternProperties": { + "^(.*)$": { + "type": "object", + "properties": { + "started": { + "type": "number" + }, + "completed": { + "type": "number" + }, + "completionRate": { + "type": "number" + } + }, + "required": [ + "started", + "completed", + "completionRate" + ] + } + } + }, + "streak": { + "type": "object", + "properties": { + "current": { + "type": "number" + }, + "longest": { + "type": "number" + }, + "activityHistory": { + "type": "object", + "patternProperties": { + "^(.*)$": { + "type": "boolean" + } + } + } + }, + "required": [ + "current", + "longest", + "activityHistory" + ] + } + }, + "required": [ + "quizzes", + "courses", + "lessons", + "streak" + ] + } + }, + "required": [ + "data" + ] } } } diff --git a/apps/web/app/api/generated-api.ts b/apps/web/app/api/generated-api.ts index 27862bba8..2adb94e7d 100644 --- a/apps/web/app/api/generated-api.ts +++ b/apps/web/app/api/generated-api.ts @@ -1142,6 +1142,26 @@ export interface CreatePaymentIntentResponse { }; } +export interface GetUserStatisticsResponse { + data: { + quizzes: { + totalAttempts: number; + totalCorrectAnswers: number; + totalWrongAnswers: number; + totalQuestions: number; + averageScore: number; + uniqueQuizzesTaken: number; + }; + courses: object; + lessons: object; + streak: { + current: number; + longest: number; + activityHistory: object; + }; + }; +} + import type { AxiosInstance, AxiosRequestConfig, @@ -2686,5 +2706,19 @@ export class API extends HttpClient + this.request({ + path: `/api/statistics`, + method: "GET", + format: "json", + ...params, + }), }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43513998d..6072b3600 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,46 +43,46 @@ importers: version: 8.4.1 '@golevelup/nestjs-stripe': specifier: ^0.8.2 - version: 0.8.2(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(rxjs@7.8.1)(stripe@17.0.0) + version: 0.8.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)(stripe@17.0.0) '@knaadh/nestjs-drizzle-postgres': specifier: ^1.0.0 - version: 1.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(drizzle-orm@0.31.2(@types/pg@8.11.6)(@types/react@18.3.4)(knex@2.5.1(mysql2@3.11.0)(pg@8.12.0))(mysql2@3.11.0)(pg@8.12.0)(postgres@3.4.4)(react@18.3.1))(postgres@3.4.4) + version: 1.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(drizzle-orm@0.31.2(@types/pg@8.11.6)(@types/react@18.3.4)(knex@2.5.1(mysql2@3.11.0)(pg@8.12.0))(mysql2@3.11.0)(pg@8.12.0)(postgres@3.4.4)(react@18.3.1))(postgres@3.4.4) '@nestjs/common': specifier: ^10.0.0 - version: 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/config': specifier: ^3.2.3 - version: 3.2.3(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) + version: 3.2.3(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/cqrs': specifier: ^10.2.7 - version: 10.2.7(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.2.7(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/jwt': specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1)) + version: 10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)) '@nestjs/passport': specifier: ^10.0.3 - version: 10.0.3(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(passport@0.7.0) + version: 10.0.3(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/platform-express': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) '@nestjs/platform-socket.io': specifier: ^10.4.4 - version: 10.4.4(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/websockets@10.4.4)(rxjs@7.8.1) + version: 10.4.4(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/websockets@10.4.4)(rxjs@7.8.1) '@nestjs/schedule': specifier: ^4.1.0 - version: 4.1.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) + version: 4.1.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1)) '@nestjs/swagger': specifier: ^7.4.0 - version: 7.4.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0) + version: 7.4.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@nestjs/terminus': specifier: ^10.2.3 - version: 10.2.3(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.2.3(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/websockets': specifier: ^10.4.4 - version: 10.4.4(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.4.4(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@repo/email-templates': specifier: workspace:* version: link:../../packages/email-templates @@ -104,6 +104,9 @@ importers: cookie-parser: specifier: ^1.4.6 version: 1.4.6 + date-fns: + specifier: ^3.6.0 + version: 3.6.0 dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -127,7 +130,7 @@ importers: version: 3.3.7 nestjs-typebox: specifier: 3.0.0-next.8 - version: 3.0.0-next.8(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/swagger@7.4.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0))(@sinclair/typebox@0.32.34)(rxjs@7.8.1) + version: 3.0.0-next.8(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/swagger@7.4.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0))(@sinclair/typebox@0.32.34)(rxjs@7.8.1) nodemailer: specifier: ^6.9.14 version: 6.9.14 @@ -167,7 +170,7 @@ importers: version: 10.0.0(chokidar@3.5.3)(typescript@5.5.4) '@nestjs/testing': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)) '@types/aws-sdk': specifier: ^2.7.0 version: 2.7.0 @@ -4694,6 +4697,9 @@ packages: '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/validator@13.12.2': + resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} + '@types/webpack@5.28.5': resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==} @@ -5563,6 +5569,12 @@ packages: cjs-module-lexer@1.3.1: resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + class-variance-authority@0.7.0: resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} @@ -7869,6 +7881,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libphonenumber-js@1.11.14: + resolution: {integrity: sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -10861,6 +10876,10 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + engines: {node: '>= 0.10'} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -12988,29 +13007,29 @@ snapshots: '@floating-ui/utils@0.2.7': {} - '@golevelup/nestjs-common@2.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))': + '@golevelup/nestjs-common@2.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) lodash: 4.17.21 nanoid: 3.3.7 - '@golevelup/nestjs-discovery@4.0.1(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': + '@golevelup/nestjs-discovery@4.0.1(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) lodash: 4.17.21 - '@golevelup/nestjs-modules@0.7.1(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': + '@golevelup/nestjs-modules@0.7.1(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) lodash: 4.17.21 rxjs: 7.8.1 - '@golevelup/nestjs-stripe@0.8.2(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(rxjs@7.8.1)(stripe@17.0.0)': + '@golevelup/nestjs-stripe@0.8.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)(stripe@17.0.0)': dependencies: - '@golevelup/nestjs-common': 2.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1)) - '@golevelup/nestjs-discovery': 4.0.1(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) - '@golevelup/nestjs-modules': 0.7.1(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) + '@golevelup/nestjs-common': 2.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)) + '@golevelup/nestjs-discovery': 4.0.1(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1)) + '@golevelup/nestjs-modules': 0.7.1(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) stripe: 17.0.0 transitivePeerDependencies: - '@nestjs/common' @@ -13270,9 +13289,9 @@ snapshots: '@jspm/core@2.0.1': {} - '@knaadh/nestjs-drizzle-postgres@1.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(drizzle-orm@0.31.2(@types/pg@8.11.6)(@types/react@18.3.4)(knex@2.5.1(mysql2@3.11.0)(pg@8.12.0))(mysql2@3.11.0)(pg@8.12.0)(postgres@3.4.4)(react@18.3.1))(postgres@3.4.4)': + '@knaadh/nestjs-drizzle-postgres@1.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(drizzle-orm@0.31.2(@types/pg@8.11.6)(@types/react@18.3.4)(knex@2.5.1(mysql2@3.11.0)(pg@8.12.0))(mysql2@3.11.0)(pg@8.12.0)(postgres@3.4.4)(react@18.3.1))(postgres@3.4.4)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) drizzle-orm: 0.31.2(@types/pg@8.11.6)(@types/react@18.3.4)(knex@2.5.1(mysql2@3.11.0)(pg@8.12.0))(mysql2@3.11.0)(pg@8.12.0)(postgres@3.4.4)(react@18.3.1) postgres: 3.4.4 tslib: 2.6.3 @@ -13358,25 +13377,28 @@ snapshots: - uglify-js - webpack-cli - '@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: iterare: 1.2.1 reflect-metadata: 0.2.0 rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/config@3.2.3(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': + '@nestjs/config@3.2.3(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) dotenv: 16.4.5 dotenv-expand: 10.0.0 lodash: 4.17.21 rxjs: 7.8.1 - '@nestjs/core@10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -13386,39 +13408,42 @@ snapshots: tslib: 2.5.3 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) - '@nestjs/websockets': 10.4.4(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) + '@nestjs/websockets': 10.4.4(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) transitivePeerDependencies: - encoding - '@nestjs/cqrs@10.2.7(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/cqrs@10.2.7(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) reflect-metadata: 0.2.0 rxjs: 7.8.1 uuid: 9.0.1 - '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))': + '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 - '@nestjs/mapped-types@2.0.5(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(reflect-metadata@0.2.0)': + '@nestjs/mapped-types@2.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) reflect-metadata: 0.2.0 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/passport@10.0.3(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(passport@0.7.0)': + '@nestjs/passport@10.0.3(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(passport@0.7.0)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) passport: 0.7.0 - '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': + '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 express: 4.18.2 @@ -13427,10 +13452,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@10.4.4(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/websockets@10.4.4)(rxjs@7.8.1)': + '@nestjs/platform-socket.io@10.4.4(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/websockets@10.4.4)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/websockets': 10.4.4(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/websockets': 10.4.4(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) rxjs: 7.8.1 socket.io: 4.7.5 tslib: 2.7.0 @@ -13439,10 +13464,10 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@4.1.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': + '@nestjs/schedule@4.1.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) cron: 3.1.7 uuid: 10.0.0 @@ -13468,46 +13493,49 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@7.4.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)': + '@nestjs/swagger@7.4.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: '@microsoft/tsdoc': 0.15.0 - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(reflect-metadata@0.2.0) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.2.0 reflect-metadata: 0.2.0 swagger-ui-dist: 5.17.14 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/terminus@10.2.3(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/terminus@10.2.3(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) boxen: 5.1.2 check-disk-space: 3.4.0 reflect-metadata: 0.2.0 rxjs: 7.8.1 - '@nestjs/testing@10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': + '@nestjs/testing@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0))': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) tslib: 2.5.3 optionalDependencies: - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) - '@nestjs/websockets@10.4.4(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/websockets@10.4.4(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-socket.io@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.0 rxjs: 7.8.1 tslib: 2.7.0 optionalDependencies: - '@nestjs/platform-socket.io': 10.4.4(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/websockets@10.4.4)(rxjs@7.8.1) + '@nestjs/platform-socket.io': 10.4.4(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/websockets@10.4.4)(rxjs@7.8.1) '@next/env@14.1.4': {} @@ -16219,6 +16247,9 @@ snapshots: '@types/uuid@10.0.0': {} + '@types/validator@13.12.2': + optional: true + '@types/webpack@5.28.5(@swc/core@1.3.101(@swc/helpers@0.5.2))(esbuild@0.19.11)': dependencies: '@types/node': 20.14.14 @@ -17502,6 +17533,16 @@ snapshots: cjs-module-lexer@1.3.1: {} + class-transformer@0.5.1: + optional: true + + class-validator@0.14.1: + dependencies: + '@types/validator': 13.12.2 + libphonenumber-js: 1.11.14 + validator: 13.12.0 + optional: true + class-variance-authority@0.7.0: dependencies: clsx: 2.0.0 @@ -18493,7 +18534,7 @@ snapshots: debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.28.1)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 @@ -18505,7 +18546,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.28.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -18536,7 +18577,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.7.4(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.28.1)(eslint@8.57.0))(eslint@8.57.0) has: 1.0.4 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -20356,6 +20397,9 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libphonenumber-js@1.11.14: + optional: true + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -21147,11 +21191,11 @@ snapshots: neo-async@2.6.2: {} - nestjs-typebox@3.0.0-next.8(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/swagger@7.4.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0))(@sinclair/typebox@0.32.34)(rxjs@7.8.1): + nestjs-typebox@3.0.0-next.8(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/swagger@7.4.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0))(@sinclair/typebox@0.32.34)(rxjs@7.8.1): dependencies: - '@nestjs/common': 10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/swagger': 7.4.0(@nestjs/common@10.0.0(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/swagger': 7.4.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(@nestjs/websockets@10.4.4)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@sinclair/typebox': 0.32.34 rxjs: 7.8.1 @@ -23976,6 +24020,9 @@ snapshots: validate-npm-package-name@5.0.1: {} + validator@13.12.0: + optional: true + vary@1.1.2: {} vfile-message@3.1.4: