This commit is contained in:
nochill 2023-02-26 16:08:28 +07:00
parent d4dfdfc5dd
commit ca73c44dfa
22 changed files with 342 additions and 34 deletions

View File

@ -2,6 +2,7 @@ CREATE TABLE "users" (
"id" VARCHAR PRIMARY KEY, "id" VARCHAR PRIMARY KEY,
"phone_number" TEXT NOT NULL UNIQUE, "phone_number" TEXT NOT NULL UNIQUE,
"email" TEXT NOT NULL UNIQUE, "email" TEXT NOT NULL UNIQUE,
"password" TEXT NOT NULL,
"fullname" TEXT, "fullname" TEXT,
"avatar" TEXT, "avatar" TEXT,
"latitude" TEXT, "latitude" TEXT,
@ -11,6 +12,7 @@ CREATE TABLE "users" (
"fcm_token" TEXT, "fcm_token" TEXT,
"is_verified" TEXT, "is_verified" TEXT,
"is_merchant" BOOLEAN, "is_merchant" BOOLEAN,
"role_id" INT,
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()

View File

@ -32,7 +32,9 @@
"dependencies": { "dependencies": {
"@nestjs/common": "^9.0.0", "@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0", "@nestjs/core": "^9.0.0",
"@nestjs/cqrs": "^9.0.3",
"@nestjs/event-emitter": "^1.4.1", "@nestjs/event-emitter": "^1.4.1",
"@nestjs/microservices": "^9.3.9",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"@slonik/migrator": "^0.11.3", "@slonik/migrator": "^0.11.3",
"cache-manager": "^5.1.6", "cache-manager": "^5.1.6",
@ -66,13 +68,16 @@
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"jest": "29.3.1", "jest": "29.3.1",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"run-script-webpack-plugin": "^0.1.1",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
"supertest": "^6.1.3", "supertest": "^6.1.3",
"ts-jest": "29.0.3", "ts-jest": "29.0.3",
"ts-loader": "^9.2.3", "ts-loader": "^9.2.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsconfig-paths": "4.1.1", "tsconfig-paths": "4.1.1",
"typescript": "^4.7.4" "typescript": "^4.7.4",
"webpack": "^5.75.0",
"webpack-node-externals": "^3.0.0"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

View File

@ -3,18 +3,37 @@ import { SlonikModule } from 'nestjs-slonik';
import { RedisClientOptions } from 'redis'; import { RedisClientOptions } from 'redis';
import { postgresConnectionUrl } from './config/database.config'; import { postgresConnectionUrl } from './config/database.config';
import { redisConfig } from './config/redis.config'; import { redisConfig } from './config/redis.config';
import { UserModule } from './user/user.module';
import { UserModule } from './modules/user/user.module'; import { UserModule } from './modules/user/user.module';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ContextInterceptor } from './libs/application/context/ContextInterceptor';
import { ExceptionInterceptor } from './libs/application/interceptors/exception.interceptors';
import { RequestContextModule } from 'nestjs-request-context';
import { CqrsModule } from '@nestjs/cqrs';
import { EventEmitterModule } from '@nestjs/event-emitter';
const interceptors = [
{
provide: APP_INTERCEPTOR,
useClass: ContextInterceptor
},
{
provide: APP_INTERCEPTOR,
useClass: ExceptionInterceptor
}
];
@Module({ @Module({
imports: [ imports: [
EventEmitterModule.forRoot(),
RequestContextModule,
SlonikModule.forRoot({ SlonikModule.forRoot({
connectionUri: postgresConnectionUrl, connectionUri: postgresConnectionUrl,
}), }),
CqrsModule,
CacheModule.register<RedisClientOptions>(redisConfig), CacheModule.register<RedisClientOptions>(redisConfig),
UserModule UserModule
], ],
controllers: [], controllers: [],
providers: [], providers: [...interceptors],
}) })
export class AppModule {} export class AppModule {}

View File

@ -22,7 +22,7 @@ export class ExceptionInterceptor implements NestInterceptor {
Array.isArray(err?.response?.message) && Array.isArray(err?.response?.message) &&
typeof err?.response?.error === 'string' && typeof err?.response?.error === 'string' &&
err.status === 400; err.status === 400;
if(isClassValidatorError) { if(isClassValidatorError) {
err = new BadRequestException( err = new BadRequestException(
new ApiErrorResponse({ new ApiErrorResponse({

View File

@ -170,7 +170,7 @@ export abstract class SqlRepositoryBase<
*/ */
protected generateInsertQuery( protected generateInsertQuery(
models: DbModel[], models: DbModel[],
): SqlSqlToken<QueryResultRow> { ): SqlSqlToken<QueryResultRow > {
// TODO: generate query from an entire array to insert multiple records at once // TODO: generate query from an entire array to insert multiple records at once
const entries = Object.entries(models[0]); const entries = Object.entries(models[0]);
const values: any = []; const values: any = [];
@ -186,7 +186,7 @@ export abstract class SqlRepositoryBase<
} }
} }
}); });
const query = sql`INSERT INTO ${sql.identifier([ const query = sql`INSERT INTO ${sql.identifier([
this.tableName, this.tableName,
])} (${sql.join(propertyNames, sql`, `)}) VALUES (${sql.join( ])} (${sql.join(propertyNames, sql`, `)}) VALUES (${sql.join(
@ -194,6 +194,7 @@ export abstract class SqlRepositoryBase<
sql`, `, sql`, `,
)})`; )})`;
const parsedQuery = query; const parsedQuery = query;
return parsedQuery; return parsedQuery;
} }

View File

@ -1,8 +1,21 @@
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
declare const module: any;
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
await app.listen(3000); await app.listen(3000);
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => app.close());
}
} }
bootstrap(); bootstrap();

View File

@ -0,0 +1,18 @@
import { Command, CommandProps } from "@src/libs/ddd/base/command.base";
export class CreateUserCommand extends Command {
readonly email: string;
readonly password: string;
readonly fullname: string;
readonly phone_number: string;
readonly role_id: number;
constructor(props: CommandProps<CreateUserCommand>) {
super(props);
this.email = props.email;
this.password = props.password;
this.phone_number = props.phone_number;
this.fullname = props.fullname;
this.role_id = props.role_id;
}
}

View File

@ -0,0 +1,32 @@
import { Post, ConflictException as ConflictHttpException, Body, Controller, InternalServerErrorException } from "@nestjs/common";
import { match, Result} from 'oxide.ts';
import { routesV1 } from "@src/config/app.routes";
import { UserAlreadyExistsError } from "../domain/user.error";
import { UserResponseDto } from "../dtos/user.response.dto";
import { CreateUserCommand } from "./create-user.command";
import { CommandBus } from "@nestjs/cqrs";
import { CreateUserRequestDto } from "./create-user.request.dto";
@Controller(routesV1.version)
export class CreateUserHttpController {
constructor(private readonly commandBus: CommandBus) {}
@Post(routesV1.user.root)
async create(@Body() body: CreateUserRequestDto): Promise<UserResponseDto> {
const command = new CreateUserCommand(body);
const result: Result<UserResponseDto, UserAlreadyExistsError> =
await this.commandBus.execute(command);
console.log(result);
return match(result, {
Ok: (res: any) => res,
Err: (error: Error) => {
if(error instanceof UserAlreadyExistsError)
throw new ConflictHttpException(error.message);
throw error;
}
})
}
}

View File

@ -0,0 +1,20 @@
import { Controller } from "@nestjs/common";
import { CommandBus } from "@nestjs/cqrs";
import { MessagePattern } from "@nestjs/microservices";
import { UserResponseDto } from "../dtos/user.response.dto";
import { CreateUserCommand } from "./create-user.command";
import { CreateUserRequestDto } from "./create-user.request.dto";
@Controller()
export class CreateUserMessageController {
constructor(private readonly commandBus: CommandBus) {}
@MessagePattern('user.create')
async create(message: CreateUserRequestDto): Promise<string> {
const command = new CreateUserCommand(message);
const id: string = await this.commandBus.execute(command);
return id;
}
}

View File

@ -0,0 +1,21 @@
import { IsEmail, IsNotEmpty, IsNumber, IsString, MaxLength, MinLength, } from "class-validator";
export class CreateUserRequestDto {
@IsEmail()
readonly email: string;
@IsNotEmpty()
@MaxLength(14)
@MinLength(10)
readonly phone_number: string
@IsNotEmpty()
readonly fullname: string;
@IsNotEmpty()
readonly password: string;
@IsNumber()
@IsNotEmpty()
readonly role_id: number;
}

View File

@ -0,0 +1,46 @@
import { ConflictException, Inject } from "@nestjs/common";
import { CommandHandler, ICommandHandler } from "@nestjs/cqrs";
import { AggregateID } from "@src/libs/ddd";
import { Err, Ok, Result } from "oxide.ts";
import { UserRepositoryPort } from "../database/user.repository.port";
import { UserEntity } from "../domain/user.entity";
import { UserAlreadyExistsError } from "../domain/user.error";
import { UserResponseDto } from "../dtos/user.response.dto";
import { USER_REPOSITORY } from "../user.di-tokens";
import { CreateUserCommand } from "./create-user.command";
@CommandHandler(CreateUserCommand)
export class CreateUserService implements ICommandHandler {
constructor(
@Inject(USER_REPOSITORY)
protected readonly userRepo: UserRepositoryPort
) {}
async execute(
command: CreateUserCommand
): Promise<Result<UserResponseDto, UserAlreadyExistsError>> {
const user = UserEntity.create({
email: command.email,
password: command.password,
fullname: command.fullname,
phone_number: command.phone_number,
role_id: command.role_id
});
try {
await this.userRepo.transaction(async () => this.userRepo.insert(user));
const userResponse = new UserResponseDto(user);
userResponse.email = command.email;
userResponse.fullname = command.fullname;
userResponse.phone_number = command.phone_number;
return Ok(userResponse)
} catch (error: any) {
if(error instanceof ConflictException) {
return Err(new UserAlreadyExistsError(error));
}
throw error;
}
}
}

View File

@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { SqlRepositoryBase } from '@src/libs/db/sql-repository.base'; import { SqlRepositoryBase } from '@src/libs/db/sql-repository.base';
import { InjectPool } from 'nestjs-slonik'; import { InjectPool } from 'nestjs-slonik';
import { DatabasePool } from 'slonik'; import { DatabasePool, sql } from 'slonik';
import { z } from 'zod'; import { z } from 'zod';
import { UserEntity } from '../domain/user.entity'; import { UserEntity } from '../domain/user.entity';
import { UserMapper } from '../user.mapper'; import { UserMapper } from '../user.mapper';
@ -12,15 +12,17 @@ export const userSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
email: z.string().email(), email: z.string().email(),
phone_number: z.string().min(10).max(15), phone_number: z.string().min(10).max(15),
password: z.string(),
fullname: z.string(), fullname: z.string(),
avatar: z.string(), avatar: z.string().optional(),
latitude: z.string(), latitude: z.string().optional(),
longitude: z.string(), longitude: z.string().optional(),
auth_token: z.string(), auth_token: z.string().optional(),
verify_token: z.string(), verify_token: z.string().optional(),
fcm_token: z.string(), fcm_token: z.string().optional(),
is_verified: z.string(), is_verified: z.string().optional(),
is_merchant: z.string(), is_merchant: z.string().optional(),
role_id: z.number(),
created_at: z.preprocess((val: any) => new Date(val), z.date()), created_at: z.preprocess((val: any) => new Date(val), z.date()),
updated_at: z.preprocess((val: any) => new Date(val), z.date()), updated_at: z.preprocess((val: any) => new Date(val), z.date()),
}); });
@ -36,10 +38,8 @@ export class UserRepository
extends SqlRepositoryBase<UserEntity, UserModel> extends SqlRepositoryBase<UserEntity, UserModel>
implements UserRepositoryPort implements UserRepositoryPort
{ {
findOneByEmail(email: string): Promise<UserEntity> { protected tableName = 'users';
throw new Error('Method not implemented.');
}
protected tableName: 'users';
protected schema = userSchema; protected schema = userSchema;
constructor( constructor(
@ -49,10 +49,14 @@ export class UserRepository
eventEmitter: EventEmitter2 eventEmitter: EventEmitter2
) { ) {
super(pool, mapper, eventEmitter, new Logger(UserRepository.name)); super(pool, mapper, eventEmitter, new Logger(UserRepository.name));
} }
async updateAddress(user: UserEntity): Promise<void> { async findOneByEmail(email: string): Promise<UserEntity> {
const address = user.getPropsCopy().address; const user = await this.pool.one(
sql.type(userSchema)`SELECT * FROM "users" WHERE email = ${email}`
)
return this.mapper.toDomain(user);
} }
} }

View File

@ -5,6 +5,7 @@ export class UserCreatedDomainEvent extends DomainEvent {
readonly phone_number: string; readonly phone_number: string;
readonly password: string; readonly password: string;
readonly fullname: string; readonly fullname: string;
readonly role_id: number;
constructor(props: DomainEventProps<UserCreatedDomainEvent>) { constructor(props: DomainEventProps<UserCreatedDomainEvent>) {
super(props); super(props);
@ -12,5 +13,6 @@ export class UserCreatedDomainEvent extends DomainEvent {
this.phone_number = props.phone_number; this.phone_number = props.phone_number;
this.password = props.password; this.password = props.password;
this.fullname = props.fullname; this.fullname = props.fullname;
this.role_id = props.role_id;
} }
} }

View File

@ -9,7 +9,7 @@ export class UserEntity extends AggregateRoot<UserProps> {
static create(create: CreateUserProps): UserEntity { static create(create: CreateUserProps): UserEntity {
const id = v4(); const id = v4();
const props: UserProps = { ...create, role: UserRoles.user }; const props: UserProps = { ...create };
const user = new UserEntity({ id, props }); const user = new UserEntity({ id, props });
user.addEvent( user.addEvent(
new UserCreatedDomainEvent({ new UserCreatedDomainEvent({
@ -17,14 +17,15 @@ export class UserEntity extends AggregateRoot<UserProps> {
email: props.email, email: props.email,
password: props.password, password: props.password,
phone_number: props.password, phone_number: props.password,
fullname: props.fullname fullname: props.fullname,
role_id: props.role_id
}) })
); );
return user; return user;
} }
get role(): UserRoles { get role(): UserRoles {
return this.props.role; return this.props.role_id;
} }
delete(): void { delete(): void {
@ -36,6 +37,6 @@ export class UserEntity extends AggregateRoot<UserProps> {
} }
public validate(): void { public validate(): void {
throw new Error("Method not implemented."); // throw new Error("Method not implemented.");
} }
} }

View File

@ -0,0 +1,11 @@
import { ExceptionBase } from "@src/libs/exceptions";
export class UserAlreadyExistsError extends ExceptionBase {
static readonly message = 'User already exists';
public readonly code = 'USER.ALREADY_EXISTS';
constructor(cause?: Error, metadata?: unknown) {
super(UserAlreadyExistsError.message, cause, metadata);
}
}

View File

@ -1,7 +1,7 @@
export interface UserProps { export interface UserProps {
password: string; password: string;
phone_number: string; phone_number: string;
role: UserRoles; role_id: UserRoles;
email: string; email: string;
fullname: string; fullname: string;
} }
@ -11,6 +11,7 @@ export interface CreateUserProps {
phone_number: string; phone_number: string;
password: string; password: string;
fullname: string; fullname: string;
role_id: number;
} }
export interface UpdateUserProps { export interface UpdateUserProps {
@ -20,6 +21,6 @@ export interface UpdateUserProps {
} }
export enum UserRoles { export enum UserRoles {
admin = 'admin', admin = 1,
user = 'user' user = 2
} }

View File

@ -0,0 +1,6 @@
import { PaginatedResponseDto } from "@src/libs/api/paginated.response.base";
import { UserResponseDto } from "./user.response.dto";
export class UserPaginatedResponseDto extends PaginatedResponseDto<UserResponseDto> {
readonly data: readonly UserResponseDto[];
}

View File

@ -0,0 +1,7 @@
import { ResponseBase } from "@src/libs/api/response.base";
export class UserResponseDto extends ResponseBase {
email: string;
phone_number: string;
fullname: string;
}

View File

@ -0,0 +1 @@
export const USER_REPOSITORY = Symbol('USER_REPOSITORY');

View File

@ -1,5 +1,50 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Mapper } from "@src/libs/ddd"; import { Mapper } from "@src/libs/ddd";
import { UserModel, userSchema } from "./database/user.repository";
import { UserEntity } from "./domain/user.entity";
import { UserResponseDto } from "./dtos/user.response.dto";
@Injectable() @Injectable()
export class UserMapper implements Mapper< export class UserMapper implements Mapper<UserEntity, UserModel, UserResponseDto> {
toPersistence(entity: UserEntity): UserModel {
const copy = entity.getPropsCopy();
const record: UserModel = {
id: copy.id,
email: copy.email,
phone_number: copy.phone_number,
password: copy.password,
role_id: copy.role_id,
fullname: copy.fullname,
created_at: copy.createdAt,
updated_at: copy.updatedAt,
}
return userSchema.parse(record)
}
toDomain(record: UserModel): UserEntity {
const entity = new UserEntity({
id: record.id,
props: {
email: record.email,
fullname: record.fullname,
password: record.password,
phone_number: record.phone_number,
role_id: record.role_id
}
});
return entity;
}
toResponse(entity: UserEntity): UserResponseDto {
const props = entity.getPropsCopy();
const response = new UserResponseDto(entity);
response.email = props.email;
response.fullname = props.fullname;
response.phone_number = props.phone_number;
return response;
}
}

View File

@ -1,4 +1,32 @@
import { Module } from '@nestjs/common'; import { Logger, Module, Provider } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { CreateUserHttpController } from './commands/create-user.http.controller';
import { CreateUserMessageController } from './commands/create-user.message.controller';
import { CreateUserService } from './commands/create-user.service';
import { UserRepository } from './database/user.repository';
import { USER_REPOSITORY } from './user.di-tokens';
import { UserMapper } from './user.mapper';
@Module({})
const httpControllers = [
CreateUserHttpController
];
const messageControllers = [CreateUserMessageController];
const commandHandlers: Provider[] = [CreateUserService];
const mappers: Provider[] = [UserMapper];
const repositories: Provider[] = [
{ provide: USER_REPOSITORY, useClass: UserRepository}
];
@Module({
imports: [CqrsModule],
controllers: [...httpControllers, ...messageControllers],
providers: [
Logger,
...repositories,
...mappers,
...commandHandlers
],
})
export class UserModule {} export class UserModule {}

View File

@ -724,6 +724,13 @@
path-to-regexp "3.2.0" path-to-regexp "3.2.0"
tslib "2.5.0" tslib "2.5.0"
"@nestjs/cqrs@^9.0.3":
version "9.0.3"
resolved "https://registry.yarnpkg.com/@nestjs/cqrs/-/cqrs-9.0.3.tgz#1ccd87feffebf33b2f3b0170f98eda8c6c74796f"
integrity sha512-hmbrqf51BVdgmnnxErnLVXfPNTEqr4Hz8DyLa9dKLIW3BuOyI5RDwJ/9sKbJ47UDBhumC5nQlNK9qk27mhqHfw==
dependencies:
uuid "9.0.0"
"@nestjs/event-emitter@^1.4.1": "@nestjs/event-emitter@^1.4.1":
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/@nestjs/event-emitter/-/event-emitter-1.4.1.tgz#a96fae678b0257b9cd8f6c82c9f3f82a0690e221" resolved "https://registry.yarnpkg.com/@nestjs/event-emitter/-/event-emitter-1.4.1.tgz#a96fae678b0257b9cd8f6c82c9f3f82a0690e221"
@ -731,6 +738,14 @@
dependencies: dependencies:
eventemitter2 "6.4.9" eventemitter2 "6.4.9"
"@nestjs/microservices@^9.3.9":
version "9.3.9"
resolved "https://registry.yarnpkg.com/@nestjs/microservices/-/microservices-9.3.9.tgz#dbcecd5c3903ee6433be303eec0415ff9e60fb81"
integrity sha512-G4EsQpOS3l2dWjJID+z/YyDPTx+SEq/5YQ/cC8XV9Hap1S0rEmo+Z1R2OdlUt+ZnkcJp7H0GIHiB0EjaYanmjA==
dependencies:
iterare "1.2.1"
tslib "2.5.0"
"@nestjs/platform-express@^9.0.0": "@nestjs/platform-express@^9.0.0":
version "9.3.9" version "9.3.9"
resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-9.3.9.tgz#557ace8589b54d4ee7bad87a1247a521058395d7" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-9.3.9.tgz#557ace8589b54d4ee7bad87a1247a521058395d7"
@ -4571,6 +4586,11 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" queue-microtask "^1.2.2"
run-script-webpack-plugin@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/run-script-webpack-plugin/-/run-script-webpack-plugin-0.1.1.tgz#dad3114be32eb864d2160306e4d9c52a2c1cfd59"
integrity sha512-PrxBRLv1K9itDKMlootSCyGhdTU+KbKGJ2wF6/k0eyo6M0YGPC58HYbS/J/QsDiwM0t7G99WcuCqto0J7omOXA==
rxjs@6.6.7, rxjs@^6.6.0: rxjs@6.6.7, rxjs@^6.6.0:
version "6.6.7" version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@ -5245,6 +5265,11 @@ uuid@8.3.2, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
v8-compile-cache-lib@^3.0.1: v8-compile-cache-lib@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
@ -5301,7 +5326,7 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
webpack-node-externals@3.0.0: webpack-node-externals@3.0.0, webpack-node-externals@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917" resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917"
integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ== integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==
@ -5311,7 +5336,7 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@5.75.0: webpack@5.75.0, webpack@^5.75.0:
version "5.75.0" version "5.75.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==