feat: add Me module with service and controller for user-specific class and room management

This commit is contained in:
Rémi 2025-01-02 18:34:17 +01:00
parent 3577925932
commit d66a2c2c35
16 changed files with 413 additions and 75 deletions

View File

@ -28,15 +28,16 @@
"@nestjs/platform-socket.io": "^10.4.12",
"@nestjs/swagger": "^8.0.7",
"@nestjs/websockets": "^10.4.12",
"@prisma/client": "^6.0.1",
"@prisma/client": "^6.1.0",
"axios": "^1.7.7",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
"moment": "^2.30.1",
"nestjs-prisma": "^0.23.0",
"prisma": "^6.0.1",
"prisma": "^6.1.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"socket.io": "^4.8.1"

View File

@ -8,15 +8,16 @@ datasource db {
}
model User {
id String @id
username String
role Role @default(STUDENT)
createdAt DateTime @default(now())
id String @id
username String
role Role @default(STUDENT)
createdAt DateTime @default(now())
Class Class[]
SentMessages UserMessage[] @relation("SentMessages")
ReceivedMessages UserMessage[] @relation("ReceivedMessages")
RoomSurveyAnswerUser RoomSurveyAnswerUser[]
RoomPresentator Room[] @relation("Presentator")
}
model UserMessage {
@ -42,32 +43,45 @@ model Class {
}
model Room {
id String @id @default(cuid())
name String
id String @id @default(cuid())
name String
date DateTime
Presentator User @relation("Presentator", fields: [presentatorId], references: [id])
presentatorId String
Class Class[]
Messages RoomMessage[]
Documents RoomDocument[]
Surveys RoomSurvey[]
Times RoomTime[]
}
model RoomTime {
id Int @id @default(autoincrement())
startTime DateTime
endTime DateTime
Room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
roomId String
}
model RoomMessage {
id Int @id @default(autoincrement())
content String
Room Room @relation(fields: [roomId], references: [id])
Room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
roomId String
}
model RoomDocument {
id Int @id @default(autoincrement())
content String
Room Room @relation(fields: [roomId], references: [id])
Room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
roomId String
}
model RoomSurvey {
id Int @id @default(autoincrement())
content String
Room Room @relation(fields: [roomId], references: [id])
Room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
roomId String
createdAt DateTime @default(now())
endAt DateTime?

View File

@ -2,12 +2,14 @@ import env from "@Config/env";
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { envValidation } from "@Validations/env.validation";
import { PrismaModule } from "nestjs-prisma";
import { AuthModule } from "@Modules/auth/auth.module";
import { UserModule } from "@Modules/user/user.module";
import { PrismaModule } from "nestjs-prisma";
import { ClassModule } from "@Modules/class/class.module";
import { MeModule } from '@Modules/me/me.module';
import { AppController } from "./app.controller";
import { ClassModule } from "./modules/class/class.module";
@Module({
imports: [
@ -22,7 +24,8 @@ import { ClassModule } from "./modules/class/class.module";
UserModule,
AuthModule,
ClassModule,
MeModule,
],
controllers: [AppController]
})
export class AppModule {}
export class AppModule { }

View File

@ -4,6 +4,7 @@ import {
Controller,
Delete,
Get,
HttpException,
Param,
Patch,
Post,
@ -31,6 +32,8 @@ import {
ApiUnauthorizedResponse,
} from "@nestjs/swagger";
import { ClassEntity } from "./entities/class.entity";
import { CreateRoomClassDto } from "./dto/create-room.dto";
import { ClassRoomEntity } from "./entities/room.entity";
@Controller("class")
@UseGuards(RolesGuard)
@ -39,7 +42,7 @@ import { ClassEntity } from "./entities/class.entity";
@Roles(["ADMIN"])
@ApiUnauthorizedResponse(UnauthorizedResponse)
export class ClassController {
constructor(private readonly classService: ClassService) {}
constructor(private readonly classService: ClassService) { }
@Post()
@ApiOkResponse(ClassResponse)
@ -55,48 +58,80 @@ export class ClassController {
@ApiOperation({ summary: "Get all classes" })
async findAll() {
return await this.classService
.findAll({})
.findAll({ include: { ClassRoom: { include: { Times: true } } } })
.then((classes) =>
classes.map((class_) => new ClassEntity(class_)),
);
}
@Get(":id")
@Get(":classId")
@ApiOkResponse(ClassResponse)
@ApiOperation({ summary: "Get a class by id" })
async findOne(@Param("id") id: string) {
async findOne(@Param("classId") classId: string) {
return await this.classService
.findOne(id)
.findOne(classId)
.then((class_) => new ClassEntity(class_));
}
@Patch(":id")
@Patch(":classId")
@ApiOkResponse(ClassResponse)
@ApiOperation({ summary: "Update a class by id" })
async update(
@Param("id") id: string,
@Param("classId") classId: string,
@Body() updateClassDto: UpdateClassDto,
) {
return await this.classService
.update(id, updateClassDto)
.update(classId, updateClassDto)
.then((class_) => new ClassEntity(class_));
}
@Delete(":id")
@Delete(":classId")
@ApiOkResponse(ClassResponse)
@ApiOperation({ summary: "Remove a class by id" })
async remove(@Param("id") id: string) {
async remove(@Param("classId") classId: string) {
return await this.classService
.remove(id)
.remove(classId)
.then((class_) => new ClassEntity(class_));
}
@Delete()
@ApiOkResponse(ClassCountResponse)
@ApiOperation({ summary: "Remove multiple classes by ids" })
@ApiQuery({ name: "ids", required: true, type: [String] })
async bulkRemove(@Query("ids") ids: string | string[]) {
if (typeof ids === "string") ids = [ids];
return await this.classService.bulkRemove(ids);
@ApiQuery({ name: "classIds", required: true, type: [String] })
async bulkRemove(@Query("classIds") classIds: string | string[]) {
if (typeof classIds === "string") classIds = [classIds];
return await this.classService.bulkRemove(classIds);
}
@Get(":classId/students")
@ApiOkResponse(ClassResponse)
@ApiOperation({ summary: "Get all students in a class" })
async getStudents(@Param("classId") classId: string) {
return await this.classService
.getStudents(classId)
.then((class_) => new ClassEntity(class_));
}
@Post(":classId/rooms")
@ApiOkResponse(ClassResponse)
@ApiOperation({ summary: "Add rooms to a class" })
async addRooms(
@Param("classId") classId: string,
@Body() createRoomClassDto: CreateRoomClassDto,
) {
return await this.classService
.createRoom(classId, createRoomClassDto)
.then((room_) => new ClassRoomEntity(room_));
}
@Get(":classId/rooms")
@ApiOkResponse(ClassResponse)
@ApiOperation({ summary: "Get rooms in a class" })
async getRooms(@Param("classId") classId: string) {
if (!classId) throw new HttpException("Class id is required", 400);
return await this.classService
.getRooms(classId)
.then((class_) => class_.map((room) => new ClassRoomEntity(room)));
}
}

View File

@ -1,12 +1,14 @@
import { Injectable } from "@nestjs/common";
import { HttpException, Injectable } from "@nestjs/common";
import { CreateClassDto } from "./dto/create-class.dto";
import { UpdateClassDto } from "./dto/update-class.dto";
import { PrismaService } from "nestjs-prisma";
import { Prisma } from "@prisma/client";
import { CreateRoomClassDto } from "./dto/create-room.dto";
import * as moment from "moment";
@Injectable()
export class ClassService {
constructor(private readonly prisma: PrismaService) {}
constructor(private readonly prisma: PrismaService) { }
async create(createClassDto: CreateClassDto) {
return await this.prisma.class.create({ data: createClassDto });
@ -18,12 +20,14 @@ export class ClassService {
cursor,
where,
orderBy,
include
}: {
skip?: number;
take?: number;
cursor?: Prisma.ClassWhereUniqueInput;
where?: Prisma.ClassWhereInput;
orderBy?: Record<string, unknown>;
include?: Prisma.ClassInclude;
}) {
return await this.prisma.class.findMany({
skip,
@ -31,6 +35,7 @@ export class ClassService {
cursor,
where,
orderBy,
include
});
}
@ -41,7 +46,9 @@ export class ClassService {
async update(id: string, updateClassDto: UpdateClassDto) {
return await this.prisma.class.update({
where: { id },
data: updateClassDto,
data: {
name: updateClassDto.name,
},
});
}
@ -54,4 +61,92 @@ export class ClassService {
where: { id: { in: ids } },
});
}
async addStudents(classId: string, studentIds: string[]) {
const Class = await this.prisma.class.findUnique({
where: { id: classId },
include: { Students: true },
});
if (!Class)
throw new HttpException("Class not found", 404);
const studentIdsToAdd = studentIds.filter(
(studentId) =>
!Class.Students.some((student) => student.id === studentId)
);
if (studentIdsToAdd.length === 0) return Class;
return await this.prisma.class.update({
where: { id: classId },
data: {
Students: {
connect: studentIdsToAdd.map((studentId) => ({ id: studentId })),
},
},
});
}
async getStudents(classId: string) {
return await this.prisma.class.findUnique({
where: { id: classId },
include: { Students: true },
});
}
async createRoom(classId: string, createRoomClassDto: CreateRoomClassDto) {
// Check if end time is greater than start time
const invalidTime = createRoomClassDto.times.find(
(time) => moment(time.start, "HH:mm").isAfter(moment(time.end, "HH:mm"))
);
if (invalidTime)
throw new HttpException("Invalid time", 400);
// Check if date is in the past
const invalidDate = moment(createRoomClassDto.date).isBefore(moment());
if (invalidDate)
throw new HttpException("Invalid date", 400);
return await this.prisma.room.create({
include: { Times: true },
data: {
name: createRoomClassDto.name,
date: moment(createRoomClassDto.date).toDate(),
presentatorId: createRoomClassDto.presentatorId,
Class: {
connect: { id: classId },
},
Times: {
createMany: {
data: createRoomClassDto.times.map((time) => ({
startTime: moment(time.start, "HH:mm").toDate(),
endTime: moment(time.end, "HH:mm").toDate(),
})),
},
},
},
});
}
async getRooms(classId: string) {
return await this.prisma.class.findUnique({
where: { id: classId },
include: { ClassRoom: { include: { Times: true, Presentator: true } } },
}).then((class_) => class_.ClassRoom);
}
async getUserClasses(userId: string) {
return await this.prisma.class.findMany({
where: {
Students: {
some: {
id: userId,
},
},
},
});
}
}

View File

@ -0,0 +1,23 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsString, Matches } from "class-validator";
export class CreateRoomClassDto {
@IsString()
@ApiProperty()
name: string;
@IsString()
@ApiProperty()
date: string;
@IsArray()
@ApiProperty()
times: {
start: string;
end: string;
}[];
@IsString()
@ApiProperty()
presentatorId: string;
}

View File

@ -1,4 +1,5 @@
import { ApiProperty, ApiSchema } from "@nestjs/swagger";
import { User } from "@prisma/client";
import { Expose } from "class-transformer";
@ApiSchema({ name: "Class" })
@ -15,6 +16,23 @@ export class ClassEntity {
@ApiProperty()
createdAt: Date;
@Expose()
@ApiProperty()
Students?: User[];
@Expose()
@ApiProperty()
ClassRoom?: {
id: string;
name: string;
date: Date;
Times?: {
startTime: Date;
endTime: Date;
}[];
}[];
constructor(partial: Partial<ClassEntity>) {
Object.assign(this, partial);
}

View File

@ -0,0 +1,33 @@
import { ApiProperty, ApiSchema } from "@nestjs/swagger";
import { User } from "@prisma/client";
import { Expose } from "class-transformer";
@ApiSchema({ name: "ClassRoom" })
export class ClassRoomEntity {
@Expose()
@ApiProperty()
id: string;
@Expose()
@ApiProperty()
name: string;
@Expose()
@ApiProperty()
date: Date;
@Expose()
@ApiProperty()
Times?: {
startTime: Date;
endTime: Date;
}[];
@Expose()
@ApiProperty()
Presentator: User;
constructor(partial: Partial<ClassRoomEntity>) {
Object.assign(this, partial);
}
}

View File

@ -0,0 +1,26 @@
import { ClassService } from '@/modules/class/class.service';
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class isUserInClassGuard implements CanActivate {
constructor(private readonly classService: ClassService) { }
async canActivate(
context: ExecutionContext,
): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const params = request.params;
const classId = params.classId;
const userId = request.user.id;
const userClasses = await this.classService.getUserClasses(userId);
if (!userClasses) {
return false;
}
const userClass = userClasses.find((class_) => class_.id === classId);
return !!userClass;
}
}

View File

@ -0,0 +1,30 @@
import { UnauthorizedResponse } from '@/ApiResponses/UnauthorizedResponse';
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { Request } from 'express';
import { JwtAuthGuard } from '../auth/guards/jwt.guard';
import { RolesGuard } from '../auth/guards/role.guard';
import { isUserInClassGuard } from './guards/isUserInClass.guard';
import { MeService } from './me.service';
@Controller('@me')
@UseGuards(RolesGuard)
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiUnauthorizedResponse(UnauthorizedResponse)
export class MeController {
constructor(private readonly meService: MeService) { }
@Get("/class")
@ApiOkResponse({ description: 'Get all classes' })
async getMyClasses(@Req() req: Request) {
return await this.meService.getMyClasses(req.user.id);
}
@Get("/class/:classId/rooms")
@UseGuards(isUserInClassGuard)
@ApiOkResponse({ description: 'Get all rooms for a class' })
async getMyRooms(@Param('classId') classId: string) {
return await this.meService.getMyRooms(classId);
}
}

View File

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { MeController } from './me.controller';
import { ClassModule } from '../class/class.module';
import { ClassService } from '../class/class.service';
import { UserModule } from '../user/user.module';
import { MeService } from './me.service';
@Module({
controllers: [MeController],
imports: [ClassModule, UserModule],
providers: [ClassService, MeService],
})
export class MeModule { }

View File

@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { ClassService } from '../class/class.service';
@Injectable()
export class MeService {
constructor(private readonly classService: ClassService) { }
async getMyClasses(userId: string) {
return await this.classService.getUserClasses(userId);
}
async getMyRooms(classId: string) {
return await this.classService.getRooms(classId)
}
}

View File

@ -10,6 +10,7 @@ import {
Param,
Patch,
Query,
Req,
UseGuards
} from "@nestjs/common";
import {
@ -28,6 +29,7 @@ import {
import { UpdateUserDTO } from "./dto/update-user.dto";
import { UserEntity } from "./entities/user.entity";
import { UserService } from "./user.service";
import { Request } from "express";
@Controller("user")
@UseGuards(RolesGuard)
@ -50,7 +52,9 @@ export class UserController {
@Get(":id")
@ApiOkResponse(UserResponse)
@ApiOperation({ summary: "Get user by id" })
async findOne(@Param("id") id: string): Promise<UserEntity> {
async findOne(@Param("id") id: string, @Req() req: Request): Promise<UserEntity> {
if (id === "@me") id = req.user.id;
return this.userService
.findOne(id)
.then((user) => new UserEntity(user));

View File

@ -7,7 +7,7 @@ import { UpdateUserDTO } from "./dto/update-user.dto";
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
constructor(private readonly prisma: PrismaService) { }
async findAll({
skip,
@ -41,7 +41,6 @@ export class UserService {
let user = await this.prisma.user.findFirst({
where: {
id,
username,
},
});
@ -55,6 +54,13 @@ export class UserService {
role: isFirstUser ? "ADMIN" : "STUDENT",
},
});
} else if (user.username !== username) {
user = await this.prisma.user.update({
where: { id },
data: {
username,
},
});
}
return user;
@ -74,6 +80,7 @@ export class UserService {
where: { id },
data: {
username: updateUserInput.username,
role: updateUserInput.role,
},
});
}

View File

@ -5,7 +5,7 @@
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"allowSyntheticDefaultImports": false,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",

View File

@ -414,46 +414,46 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@prisma/client@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.0.1.tgz#e24c5a44fb46d04a92426879bd9f8a2c28338420"
integrity sha512-60w7kL6bUxz7M6Gs/V+OWMhwy94FshpngVmOY05TmGD0Lhk+Ac0ZgtjlL6Wll9TD4G03t4Sq1wZekNVy+Xdlbg==
"@prisma/client@^6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.1.0.tgz#179d3b70586e7be522f6f1f0a82cca01396f719a"
integrity sha512-AbQYc5+EJKm1Ydfq3KxwcGiy7wIbm4/QbjCKWWoNROtvy7d6a3gmAGkKjK0iUCzh+rHV8xDhD5Cge8ke/kiy5Q==
"@prisma/debug@6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.0.1.tgz#8407544dd89c8bf85a6e9889ea263fe0e1ccebe2"
integrity sha512-jQylgSOf7ibTVxqBacnAlVGvek6fQxJIYCQOeX2KexsfypNzXjJQSS2o5s+Mjj2Np93iSOQUaw6TvPj8syhG4w==
"@prisma/debug@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.1.0.tgz#a27a1d144f72a3bc95061ecb0255e7554d9d59ec"
integrity sha512-0himsvcM4DGBTtvXkd2Tggv6sl2JyUYLzEGXXleFY+7Kp6rZeSS3hiTW9mwtUlXrwYbJP6pwlVNB7jYElrjWUg==
"@prisma/engines-version@5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e":
version "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e.tgz#2db5a05d014aac504e8574da6b96ac3d9a617526"
integrity sha512-JmIds0Q2/vsOmnuTJYxY4LE+sajqjYKhLtdOT6y4imojqv5d/aeVEfbBGC74t8Be1uSp0OP8lxIj2OqoKbLsfQ==
"@prisma/engines-version@6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959":
version "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959.tgz#0b21ebf57362ffe35d0760c39855f90bbfa0f2fd"
integrity sha512-PdJqmYM2Fd8K0weOOtQThWylwjsDlTig+8Pcg47/jszMuLL9iLIaygC3cjWJLda69siRW4STlCTMSgOjZzvKPQ==
"@prisma/engines@6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-6.0.1.tgz#b64afa9b4e2bedc2b6488b15f9d867c8672b33ee"
integrity sha512-4hxzI+YQIR2uuDyVsDooFZGu5AtixbvM2psp+iayDZ4hRrAHo/YwgA17N23UWq7G6gRu18NvuNMb48qjP3DPQw==
"@prisma/engines@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-6.1.0.tgz#2195244a8ce33839a8131e4465624e21d1f8d042"
integrity sha512-GnYJbCiep3Vyr1P/415ReYrgJUjP79fBNc1wCo7NP6Eia0CzL2Ot9vK7Infczv3oK7JLrCcawOSAxFxNFsAERQ==
dependencies:
"@prisma/debug" "6.0.1"
"@prisma/engines-version" "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e"
"@prisma/fetch-engine" "6.0.1"
"@prisma/get-platform" "6.0.1"
"@prisma/debug" "6.1.0"
"@prisma/engines-version" "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959"
"@prisma/fetch-engine" "6.1.0"
"@prisma/get-platform" "6.1.0"
"@prisma/fetch-engine@6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-6.0.1.tgz#1e3affb7a749caaf366239c7d61ae7b19d3e7e00"
integrity sha512-T36bWFVGeGYYSyYOj9d+O9G3sBC+pAyMC+jc45iSL63/Haq1GrYjQPgPMxrEj9m739taXrupoysRedQ+VyvM/Q==
"@prisma/fetch-engine@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-6.1.0.tgz#2a5174787bf57c9b1d5d400bb923e0dc6a73a794"
integrity sha512-asdFi7TvPlEZ8CzSZ/+Du5wZ27q6OJbRSXh+S8ISZguu+S9KtS/gP7NeXceZyb1Jv1SM1S5YfiCv+STDsG6rrg==
dependencies:
"@prisma/debug" "6.0.1"
"@prisma/engines-version" "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e"
"@prisma/get-platform" "6.0.1"
"@prisma/debug" "6.1.0"
"@prisma/engines-version" "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959"
"@prisma/get-platform" "6.1.0"
"@prisma/get-platform@6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-6.0.1.tgz#322dae7e8862c9b849384b820b81ecd4006e1d63"
integrity sha512-zspC9vlxAqx4E6epMPMLLBMED2VD8axDe8sPnquZ8GOsn6tiacWK0oxrGK4UAHYzYUVuMVUApJbdXB2dFpLhvg==
"@prisma/get-platform@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-6.1.0.tgz#d4394a24ef91af6675a92382ed40e6e6e07eeb13"
integrity sha512-ia8bNjboBoHkmKGGaWtqtlgQOhCi7+f85aOkPJKgNwWvYrT6l78KgojLekE8zMhVk0R9lWcifV0Pf8l3/15V0Q==
dependencies:
"@prisma/debug" "6.0.1"
"@prisma/debug" "6.1.0"
"@scarf/scarf@=1.4.0":
version "1.4.0"
@ -2563,6 +2563,11 @@ mkdirp@^0.5.4:
dependencies:
minimist "^1.2.6"
moment@^2.30.1:
version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -2821,12 +2826,12 @@ prettier@^3.0.0:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f"
integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==
prisma@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.0.1.tgz#63bf769c798b98986aeaf9cddbf12193f9e2c356"
integrity sha512-CaMNFHkf+DDq8zq3X/JJsQ4Koy7dyWwwtOKibkT/Am9j/tDxcfbg7+lB1Dzhx18G/+RQCMgjPYB61bhRqteNBQ==
prisma@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.1.0.tgz#738f657fdd5ab8e6775f385db81bf7e61c70fbaf"
integrity sha512-aFI3Yi+ApUxkwCJJwyQSwpyzUX7YX3ihzuHNHOyv4GJg3X5tQsmRaJEnZ+ZyfHpMtnyahhmXVfbTZ+lS8ZtfKw==
dependencies:
"@prisma/engines" "6.0.1"
"@prisma/engines" "6.1.0"
optionalDependencies:
fsevents "2.3.3"
@ -3161,7 +3166,16 @@ streamsearch@^1.1.0:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -3193,7 +3207,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==