Compare commits

..

No commits in common. "development" and "main" have entirely different histories.

17 changed files with 124 additions and 225 deletions

View File

@ -1,16 +1,16 @@
{ {
"name": "@toogether/api", "name": "@toogether/server",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"build": "nest build", "build": "nest build",
"build:docker": "docker build -t toogether/api .", "build:docker": "docker build --no-cache -t toogether/api .",
"format": "prettier --write \"src/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\"",
"start": "nest start", "start": "nest start",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "docker compose up --force-recreate -d", "start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\"", "preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\"",
"migrate:dev": "npx prisma migrate dev", "migrate:dev": "npx prisma migrate dev",
@ -29,7 +29,7 @@
"@nestjs/platform-socket.io": "^10.4.12", "@nestjs/platform-socket.io": "^10.4.12",
"@nestjs/swagger": "^8.0.7", "@nestjs/swagger": "^8.0.7",
"@nestjs/websockets": "^10.4.12", "@nestjs/websockets": "^10.4.12",
"@prisma/client": "^6.2.1", "@prisma/client": "^6.1.0",
"axios": "^1.7.7", "axios": "^1.7.7",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
@ -37,9 +37,8 @@
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0", "jwks-rsa": "^3.1.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.5.46",
"nestjs-prisma": "^0.23.0", "nestjs-prisma": "^0.23.0",
"prisma": "^6.2.1", "prisma": "^6.1.0",
"reflect-metadata": "^0.2.0", "reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"socket.io": "^4.8.1" "socket.io": "^4.8.1"

View File

@ -10,6 +10,7 @@ datasource db {
model User { model User {
id String @id id String @id
username String username String
role Role @default(STUDENT)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
Class Class[] Class Class[]
@ -44,7 +45,7 @@ model Class {
model Room { model Room {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
date DateTime @db.Date date DateTime
Presentator User @relation("Presentator", fields: [presentatorId], references: [id]) Presentator User @relation("Presentator", fields: [presentatorId], references: [id])
presentatorId String presentatorId String
@ -57,8 +58,8 @@ model Room {
model RoomTime { model RoomTime {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
startTime DateTime @db.Time startTime DateTime
endTime DateTime @db.Time endTime DateTime
Room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) Room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
roomId String roomId String
} }
@ -104,3 +105,8 @@ model RoomSurveyAnswerUser {
Answer RoomSurveyAnswer @relation(fields: [answerId], references: [id]) Answer RoomSurveyAnswer @relation(fields: [answerId], references: [id])
answerId Int answerId Int
} }
enum Role {
STUDENT
ADMIN
}

View File

@ -1,3 +1,4 @@
import { SetMetadata } from "@nestjs/common"; import { SetMetadata } from "@nestjs/common";
import { $Enums } from "@prisma/client";
export const Roles = (roles: string[]) => SetMetadata("roles", roles); export const Roles = (roles: $Enums.Role[]) => SetMetadata("roles", roles);

View File

@ -7,13 +7,12 @@ import {
} from "@nestjs/common"; } from "@nestjs/common";
import { Reflector } from "@nestjs/core"; import { Reflector } from "@nestjs/core";
import { Request } from "express"; import { Request } from "express";
import { decode, JwtPayload, UserJwtPayload } from "jsonwebtoken";
@Injectable() @Injectable()
export class RolesGuard implements CanActivate { export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {} constructor(private readonly reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> { canActivate(context: ExecutionContext): boolean {
const RolesHandler = this.reflector.get<string[]>( const RolesHandler = this.reflector.get<string[]>(
"roles", "roles",
context.getHandler(), context.getHandler(),
@ -30,21 +29,11 @@ export class RolesGuard implements CanActivate {
if (!user) throw new ForbiddenException("User not authenticated"); if (!user) throw new ForbiddenException("User not authenticated");
const decodedToken = await this.decodeToken(
this.extractTokenFromHeader(request.headers.authorization),
);
// Check if the user has the right role
// On the handler level
const hasRoleHandler = const hasRoleHandler =
RolesHandler?.some((role) => RolesHandler?.some((role) => user.role?.includes(role)) ??
decodedToken.realm_access.roles?.includes(role), false,
) ?? false,
// On the class level
hasRoleClass = hasRoleClass =
RolesClass?.some((role) => RolesClass?.some((role) => user.role?.includes(role)) ?? false;
decodedToken.realm_access.roles?.includes(role),
) ?? false;
if (hasRoleHandler) return true; if (hasRoleHandler) return true;
else if (hasRoleClass) return true; else if (hasRoleClass) return true;
@ -53,20 +42,4 @@ export class RolesGuard implements CanActivate {
`User doesn't have the right role, expected: ${RolesHandler ?? RolesClass}`, `User doesn't have the right role, expected: ${RolesHandler ?? RolesClass}`,
); );
} }
async decodeToken(token: string) {
try {
return decode(token) as UserJwtPayload;
} catch (error) {
throw new UnauthorizedException("Invalid token");
}
}
extractTokenFromHeader(header: string) {
const token = header.split(" ")[1];
if (!token) throw new UnauthorizedException("Token not found");
return token;
}
} }

View File

@ -39,7 +39,7 @@ import { ClassRoomEntity } from "./entities/room.entity";
@UseGuards(RolesGuard) @UseGuards(RolesGuard)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Roles(["admin"]) @Roles(["ADMIN"])
@ApiUnauthorizedResponse(UnauthorizedResponse) @ApiUnauthorizedResponse(UnauthorizedResponse)
export class ClassController { export class ClassController {
constructor(private readonly classService: ClassService) { } constructor(private readonly classService: ClassService) { }

View File

@ -4,13 +4,11 @@ import { UpdateClassDto } from "./dto/update-class.dto";
import { PrismaService } from "nestjs-prisma"; import { PrismaService } from "nestjs-prisma";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { CreateRoomClassDto } from "./dto/create-room.dto"; import { CreateRoomClassDto } from "./dto/create-room.dto";
import * as moment from "moment-timezone"; import * as moment from "moment";
moment.tz.setDefault("UTC");
@Injectable() @Injectable()
export class ClassService { export class ClassService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) { }
async create(createClassDto: CreateClassDto) { async create(createClassDto: CreateClassDto) {
return await this.prisma.class.create({ data: createClassDto }); return await this.prisma.class.create({ data: createClassDto });
@ -22,7 +20,7 @@ export class ClassService {
cursor, cursor,
where, where,
orderBy, orderBy,
include, include
}: { }: {
skip?: number; skip?: number;
take?: number; take?: number;
@ -37,7 +35,7 @@ export class ClassService {
cursor, cursor,
where, where,
orderBy, orderBy,
include, include
}); });
} }
@ -70,11 +68,12 @@ export class ClassService {
include: { Students: true }, include: { Students: true },
}); });
if (!Class) throw new HttpException("Class not found", 404); if (!Class)
throw new HttpException("Class not found", 404);
const studentIdsToAdd = studentIds.filter( const studentIdsToAdd = studentIds.filter(
(studentId) => (studentId) =>
!Class.Students.some((student) => student.id === studentId), !Class.Students.some((student) => student.id === studentId)
); );
if (studentIdsToAdd.length === 0) return Class; if (studentIdsToAdd.length === 0) return Class;
@ -83,9 +82,7 @@ export class ClassService {
where: { id: classId }, where: { id: classId },
data: { data: {
Students: { Students: {
connect: studentIdsToAdd.map((studentId) => ({ connect: studentIdsToAdd.map((studentId) => ({ id: studentId })),
id: studentId,
})),
}, },
}, },
}); });
@ -100,40 +97,18 @@ export class ClassService {
async createRoom(classId: string, createRoomClassDto: CreateRoomClassDto) { async createRoom(classId: string, createRoomClassDto: CreateRoomClassDto) {
// Check if end time is greater than start time // Check if end time is greater than start time
const invalidTime = createRoomClassDto.times.find((time, i) => const invalidTime = createRoomClassDto.times.find(
moment(time.start, "HH:mm").isAfter(moment(time.end, "HH:mm")), (time) => moment(time.start, "HH:mm").isAfter(moment(time.end, "HH:mm"))
); );
if (invalidTime) if (invalidTime)
throw new HttpException( throw new HttpException("Invalid time", 400);
`The end time must be greater than the start time, ${invalidTime.start} - ${invalidTime.end}`,
400,
);
const date = moment(createRoomClassDto.date); // Check if date is in the past
const invalidDate = moment(createRoomClassDto.date).isBefore(moment());
// Check if the date is before the current date if (invalidDate)
if (date.isBefore(moment().startOf("day"))) throw new HttpException("Invalid date", 400);
throw new HttpException(
"Can't create a room for a class that has passed",
400,
);
const parseTimes = createRoomClassDto.times.map((time) => ({
start: moment(date).set("hour", moment(time.start, "HH:mm").hour()),
end: moment(date).set("hour", moment(time.end, "HH:mm").hour()),
}));
const lastTimeEnd = parseTimes.reduce((prev, current) =>
moment(current.end).isAfter(moment(prev.end)) ? current : prev,
);
// Check if the last time end is before the current time
if (moment(lastTimeEnd.end).isBefore(moment().subtract(5, "minutes")))
throw new HttpException(
"Can't create a room for a class that has already ended",
400,
);
return await this.prisma.room.create({ return await this.prisma.room.create({
include: { Times: true }, include: { Times: true },
@ -157,14 +132,10 @@ export class ClassService {
} }
async getRooms(classId: string) { async getRooms(classId: string) {
return await this.prisma.class return await this.prisma.class.findUnique({
.findUnique({
where: { id: classId }, where: { id: classId },
include: { include: { ClassRoom: { include: { Times: true, Presentator: true } } },
ClassRoom: { include: { Times: true, Presentator: true } }, }).then((class_) => class_.ClassRoom);
},
})
.then((class_) => class_.ClassRoom);
} }
async getUserClasses(userId: string) { async getUserClasses(userId: string) {

View File

@ -1,12 +1,12 @@
import { ApiProperty } from "@nestjs/swagger"; import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsDateString, IsString, Matches } from "class-validator"; import { IsArray, IsString, Matches } from "class-validator";
export class CreateRoomClassDto { export class CreateRoomClassDto {
@IsString() @IsString()
@ApiProperty() @ApiProperty()
name: string; name: string;
@IsDateString() @IsString()
@ApiProperty() @ApiProperty()
date: string; date: string;

View File

@ -6,7 +6,6 @@ import { JwtAuthGuard } from '../auth/guards/jwt.guard';
import { RolesGuard } from '../auth/guards/role.guard'; import { RolesGuard } from '../auth/guards/role.guard';
import { isUserInClassGuard } from './guards/isUserInClass.guard'; import { isUserInClassGuard } from './guards/isUserInClass.guard';
import { MeService } from './me.service'; import { MeService } from './me.service';
import { UserService } from '../user/user.service';
@Controller('@me') @Controller('@me')
@UseGuards(RolesGuard) @UseGuards(RolesGuard)
@ -14,16 +13,7 @@ import { UserService } from '../user/user.service';
@ApiBearerAuth() @ApiBearerAuth()
@ApiUnauthorizedResponse(UnauthorizedResponse) @ApiUnauthorizedResponse(UnauthorizedResponse)
export class MeController { export class MeController {
constructor( constructor(private readonly meService: MeService) { }
private readonly meService: MeService,
private readonly userService: UserService,
) { }
@Get()
@ApiOkResponse({ description: 'Get my profile' })
async getMyProfile(@Req() req: Request) {
return await this.userService.getProfile(req.user.id);
}
@Get("/class") @Get("/class")
@ApiOkResponse({ description: 'Get all classes' }) @ApiOkResponse({ description: 'Get all classes' })

View File

@ -3,9 +3,7 @@ import { ClassService } from '../class/class.service';
@Injectable() @Injectable()
export class MeService { export class MeService {
constructor( constructor(private readonly classService: ClassService) { }
private readonly classService: ClassService,
) { }
async getMyClasses(userId: string) { async getMyClasses(userId: string) {
return await this.classService.getUserClasses(userId); return await this.classService.getUserClasses(userId);

View File

@ -11,13 +11,6 @@ export const UserResponse = {
id: "1", id: "1",
role: "ADMIN", role: "ADMIN",
username: "admin", username: "admin",
Class: [
{
id: "1",
name: "Class 1",
createdAt: new Date(),
},
],
} as UserEntity, } as UserEntity,
}, },
}, },
@ -30,8 +23,8 @@ export const UsersResponse = {
example: { example: {
summary: "A list of users", summary: "A list of users",
value: [ value: [
{ id: "1", username: "admin" }, { id: "1", role: "ADMIN", username: "admin" },
{ id: "2", username: "student" }, { id: "2", role: "STUDENT", username: "student" },
] as UserEntity[], ] as UserEntity[],
}, },
}, },

View File

@ -1,4 +1,5 @@
import { ApiProperty } from "@nestjs/swagger"; import { ApiProperty } from "@nestjs/swagger";
import { Role } from "@prisma/client";
import { IsString } from "class-validator"; import { IsString } from "class-validator";
export class CreateUserDTO { export class CreateUserDTO {
@ -9,4 +10,8 @@ export class CreateUserDTO {
@IsString() @IsString()
@ApiProperty() @ApiProperty()
username: string; username: string;
@IsString()
@ApiProperty()
role: Role;
} }

View File

@ -1,5 +1,5 @@
import { ClassEntity } from "@/modules/class/entities/class.entity";
import { ApiProperty, ApiSchema } from "@nestjs/swagger"; import { ApiProperty, ApiSchema } from "@nestjs/swagger";
import { $Enums } from "@prisma/client";
import { Expose } from "class-transformer"; import { Expose } from "class-transformer";
@ApiSchema({ name: "User" }) @ApiSchema({ name: "User" })
@ -14,7 +14,7 @@ export class UserEntity {
@Expose() @Expose()
@ApiProperty() @ApiProperty()
Class?: ClassEntity[]; role: $Enums.Role;
constructor(partial: Partial<UserEntity>) { constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -11,7 +11,7 @@ import {
Patch, Patch,
Query, Query,
Req, Req,
UseGuards, UseGuards
} from "@nestjs/common"; } from "@nestjs/common";
import { import {
ApiBearerAuth, ApiBearerAuth,
@ -19,9 +19,8 @@ import {
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiQuery, ApiQuery,
ApiUnauthorizedResponse, ApiUnauthorizedResponse
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { Request } from "express";
import { import {
UserCountResponse, UserCountResponse,
UserResponse, UserResponse,
@ -30,12 +29,13 @@ import {
import { UpdateUserDTO } from "./dto/update-user.dto"; import { UpdateUserDTO } from "./dto/update-user.dto";
import { UserEntity } from "./entities/user.entity"; import { UserEntity } from "./entities/user.entity";
import { UserService } from "./user.service"; import { UserService } from "./user.service";
import { Request } from "express";
@Controller("user") @Controller("user")
@UseGuards(RolesGuard) @UseGuards(RolesGuard)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Roles(["admin"]) @Roles(["ADMIN"])
@ApiUnauthorizedResponse(UnauthorizedResponse) @ApiUnauthorizedResponse(UnauthorizedResponse)
export class UserController { export class UserController {
constructor(private readonly userService: UserService) {} constructor(private readonly userService: UserService) {}
@ -45,21 +45,14 @@ export class UserController {
@ApiOperation({ summary: "Get all users" }) @ApiOperation({ summary: "Get all users" })
async findAll(): Promise<UserEntity[]> { async findAll(): Promise<UserEntity[]> {
return await this.userService return await this.userService
.findAll({ .findAll()
include: {
Class: true,
},
})
.then((users) => users.map((user) => new UserEntity(user))); .then((users) => users.map((user) => new UserEntity(user)));
} }
@Get(":id") @Get(":id")
@ApiOkResponse(UserResponse) @ApiOkResponse(UserResponse)
@ApiOperation({ summary: "Get user by id" }) @ApiOperation({ summary: "Get user by id" })
async findOne( async findOne(@Param("id") id: string, @Req() req: Request): Promise<UserEntity> {
@Param("id") id: string,
@Req() req: Request,
): Promise<UserEntity> {
if (id === "@me") id = req.user.id; if (id === "@me") id = req.user.id;
return this.userService return this.userService

View File

@ -7,7 +7,7 @@ import { UpdateUserDTO } from "./dto/update-user.dto";
@Injectable() @Injectable()
export class UserService { export class UserService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) { }
async findAll({ async findAll({
skip, skip,
@ -15,14 +15,12 @@ export class UserService {
cursor, cursor,
where, where,
orderBy, orderBy,
include,
}: { }: {
skip?: number; skip?: number;
take?: number; take?: number;
cursor?: Prisma.UserWhereUniqueInput; cursor?: Prisma.UserWhereUniqueInput;
where?: Prisma.UserWhereInput; where?: Prisma.UserWhereInput;
orderBy?: Record<string, unknown>; orderBy?: Record<string, unknown>;
include?: Prisma.UserInclude;
} = {}) { } = {}) {
return await this.prisma.user.findMany({ return await this.prisma.user.findMany({
skip, skip,
@ -30,7 +28,6 @@ export class UserService {
cursor, cursor,
where, where,
orderBy, orderBy,
include,
}); });
} }
@ -48,10 +45,13 @@ export class UserService {
}); });
if (!user) { if (!user) {
const isFirstUser = (await this.prisma.user.count()) === 0;
user = await this.prisma.user.create({ user = await this.prisma.user.create({
data: { data: {
id, id,
username, username,
role: isFirstUser ? "ADMIN" : "STUDENT",
}, },
}); });
} else if (user.username !== username) { } else if (user.username !== username) {
@ -80,6 +80,7 @@ export class UserService {
where: { id }, where: { id },
data: { data: {
username: updateUserInput.username, username: updateUserInput.username,
role: updateUserInput.role,
}, },
}); });
} }
@ -99,14 +100,4 @@ export class UserService {
}, },
}); });
} }
async getProfile(id: string) {
return await this.prisma.user.findUnique({
where: { id },
select: {
id: true,
username: true,
},
});
}
} }

2
src/types/http.d.ts vendored
View File

@ -4,6 +4,6 @@ import { IncomingMessage } from "http";
declare module "http" { declare module "http" {
interface IncomingMessage { interface IncomingMessage {
user?: User; user?: UserEntity;
} }
} }

14
src/types/jwt.d.ts vendored
View File

@ -1,14 +0,0 @@
import * as jwt from "jsonwebtoken";
declare module "jsonwebtoken" {
export interface UserJwtPayload extends jwt.JwtPayload {
name: string;
preferred_username: string;
email: string;
given_name: string;
family_name: string;
realm_access: {
roles: string[];
};
}
}

View File

@ -414,46 +414,46 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@prisma/client@^6.2.1": "@prisma/client@^6.1.0":
version "6.2.1" version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.2.1.tgz#3d7d0c8669bba490247e1ffff67b93a516bd789f" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.1.0.tgz#179d3b70586e7be522f6f1f0a82cca01396f719a"
integrity sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA== integrity sha512-AbQYc5+EJKm1Ydfq3KxwcGiy7wIbm4/QbjCKWWoNROtvy7d6a3gmAGkKjK0iUCzh+rHV8xDhD5Cge8ke/kiy5Q==
"@prisma/debug@6.2.1": "@prisma/debug@6.1.0":
version "6.2.1" version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.2.1.tgz#887719967c4942d125262e48f6c47c45d17c1f61" resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.1.0.tgz#a27a1d144f72a3bc95061ecb0255e7554d9d59ec"
integrity sha512-0KItvt39CmQxWkEw6oW+RQMD6RZ43SJWgEUnzxN8VC9ixMysa7MzZCZf22LCK5DSooiLNf8vM3LHZm/I/Ni7bQ== integrity sha512-0himsvcM4DGBTtvXkd2Tggv6sl2JyUYLzEGXXleFY+7Kp6rZeSS3hiTW9mwtUlXrwYbJP6pwlVNB7jYElrjWUg==
"@prisma/engines-version@6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69": "@prisma/engines-version@6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959":
version "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69" version "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69.tgz#b84ce3fab44bfa13a22669da02752330b61745b2" resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959.tgz#0b21ebf57362ffe35d0760c39855f90bbfa0f2fd"
integrity sha512-7tw1qs/9GWSX6qbZs4He09TOTg1ff3gYsB3ubaVNN0Pp1zLm9NC5C5MZShtkz7TyQjx7blhpknB7HwEhlG+PrQ== integrity sha512-PdJqmYM2Fd8K0weOOtQThWylwjsDlTig+8Pcg47/jszMuLL9iLIaygC3cjWJLda69siRW4STlCTMSgOjZzvKPQ==
"@prisma/engines@6.2.1": "@prisma/engines@6.1.0":
version "6.2.1" version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-6.2.1.tgz#14ef56bb780f02871a728667161d997a14aedb69" resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-6.1.0.tgz#2195244a8ce33839a8131e4465624e21d1f8d042"
integrity sha512-lTBNLJBCxVT9iP5I7Mn6GlwqAxTpS5qMERrhebkUhtXpGVkBNd/jHnNJBZQW4kGDCKaQg/r2vlJYkzOHnAb7ZQ== integrity sha512-GnYJbCiep3Vyr1P/415ReYrgJUjP79fBNc1wCo7NP6Eia0CzL2Ot9vK7Infczv3oK7JLrCcawOSAxFxNFsAERQ==
dependencies: dependencies:
"@prisma/debug" "6.2.1" "@prisma/debug" "6.1.0"
"@prisma/engines-version" "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69" "@prisma/engines-version" "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959"
"@prisma/fetch-engine" "6.2.1" "@prisma/fetch-engine" "6.1.0"
"@prisma/get-platform" "6.2.1" "@prisma/get-platform" "6.1.0"
"@prisma/fetch-engine@6.2.1": "@prisma/fetch-engine@6.1.0":
version "6.2.1" version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-6.2.1.tgz#cd7eb7428a407105e0f3761dba536aefd41fc7f7" resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-6.1.0.tgz#2a5174787bf57c9b1d5d400bb923e0dc6a73a794"
integrity sha512-OO7O9d6Mrx2F9i+Gu1LW+DGXXyUFkP7OE5aj9iBfA/2jjDXEJjqa9X0ZmM9NZNo8Uo7ql6zKm6yjDcbAcRrw1A== integrity sha512-asdFi7TvPlEZ8CzSZ/+Du5wZ27q6OJbRSXh+S8ISZguu+S9KtS/gP7NeXceZyb1Jv1SM1S5YfiCv+STDsG6rrg==
dependencies: dependencies:
"@prisma/debug" "6.2.1" "@prisma/debug" "6.1.0"
"@prisma/engines-version" "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69" "@prisma/engines-version" "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959"
"@prisma/get-platform" "6.2.1" "@prisma/get-platform" "6.1.0"
"@prisma/get-platform@6.2.1": "@prisma/get-platform@6.1.0":
version "6.2.1" version "6.1.0"
resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-6.2.1.tgz#34313cd0ee3587798ad33a7b57b6342dc8e66426" resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-6.1.0.tgz#d4394a24ef91af6675a92382ed40e6e6e07eeb13"
integrity sha512-zp53yvroPl5m5/gXYLz7tGCNG33bhG+JYCm74ohxOq1pPnrL47VQYFfF3RbTZ7TzGWCrR3EtoiYMywUBw7UK6Q== integrity sha512-ia8bNjboBoHkmKGGaWtqtlgQOhCi7+f85aOkPJKgNwWvYrT6l78KgojLekE8zMhVk0R9lWcifV0Pf8l3/15V0Q==
dependencies: dependencies:
"@prisma/debug" "6.2.1" "@prisma/debug" "6.1.0"
"@scarf/scarf@=1.4.0": "@scarf/scarf@=1.4.0":
version "1.4.0" version "1.4.0"
@ -2563,14 +2563,7 @@ mkdirp@^0.5.4:
dependencies: dependencies:
minimist "^1.2.6" minimist "^1.2.6"
moment-timezone@^0.5.46: moment@^2.30.1:
version "0.5.46"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.46.tgz#a21aa6392b3c6b3ed916cd5e95858a28d893704a"
integrity sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==
dependencies:
moment "^2.29.4"
moment@^2.29.4, moment@^2.30.1:
version "2.30.1" version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
@ -2833,12 +2826,12 @@ prettier@^3.0.0:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f"
integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==
prisma@^6.2.1: prisma@^6.1.0:
version "6.2.1" version "6.1.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.2.1.tgz#457b210326d66d0e6f583cc6f9cd2819b984408f" resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.1.0.tgz#738f657fdd5ab8e6775f385db81bf7e61c70fbaf"
integrity sha512-hhyM0H13pQleQ+br4CkzGizS5I0oInoeTw3JfLw1BRZduBSQxPILlJLwi+46wZzj9Je7ndyQEMGw/n5cN2fknA== integrity sha512-aFI3Yi+ApUxkwCJJwyQSwpyzUX7YX3ihzuHNHOyv4GJg3X5tQsmRaJEnZ+ZyfHpMtnyahhmXVfbTZ+lS8ZtfKw==
dependencies: dependencies:
"@prisma/engines" "6.2.1" "@prisma/engines" "6.1.0"
optionalDependencies: optionalDependencies:
fsevents "2.3.3" fsevents "2.3.3"