add JWT authorization
This commit is contained in:
parent
8024b36280
commit
10ba9e5550
@ -23,12 +23,18 @@
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@prisma/client": "^6.9.0",
|
||||
"argon2": "^0.43.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"prisma": "^6.9.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
@ -41,9 +47,12 @@
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@swc/cli": "^0.6.0",
|
||||
"@swc/core": "^1.10.7",
|
||||
"@types/cookie-parser": "^1.4.9",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
|
||||
@ -8,77 +8,16 @@ datasource db {
|
||||
url = env("POSTGRES_URI")
|
||||
}
|
||||
|
||||
model Movie {
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
|
||||
title String
|
||||
description String?
|
||||
|
||||
releaseYear Int @map("release_year")
|
||||
rating Float @default(0.0)
|
||||
|
||||
isAvailabel Boolean @default(false) @map("is_available")
|
||||
|
||||
genre Genre @default(DRAMMA)
|
||||
|
||||
poster MoviePoster @relation(fields: [posterId], references: [id])
|
||||
posterId String @unique @map("poster_id")
|
||||
|
||||
reviews Review[] @relation("movie_reviews")
|
||||
actors Actor[] @relation("movie_actors")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([releaseYear, title])
|
||||
@@map("movies")
|
||||
}
|
||||
|
||||
model MoviePoster {
|
||||
id String @id @default(uuid())
|
||||
|
||||
url String
|
||||
|
||||
movie Movie? @relation()
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("movie_posters")
|
||||
}
|
||||
|
||||
model Review {
|
||||
id String @id @default(uuid())
|
||||
|
||||
text String
|
||||
rating Decimal @default(0.0)
|
||||
|
||||
movie Movie @relation("movie_reviews", fields: [movieId], references: [id], onDelete: Cascade)
|
||||
movieId String @map("movie_id")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("reviews")
|
||||
}
|
||||
|
||||
model Actor {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
password String
|
||||
|
||||
name String
|
||||
movies Movie[] @relation("movie_actors")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
createdAt DateTime @default(now()) @map("create_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("actors")
|
||||
}
|
||||
|
||||
enum Genre {
|
||||
ACTION
|
||||
COMEDY
|
||||
DRAMMA
|
||||
HORROR
|
||||
|
||||
@@map("enum_genres")
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ActorService } from './actor.service';
|
||||
import { CreateActorDto } from './dto/create-actor.dto';
|
||||
|
||||
@Controller('actors')
|
||||
export class ActorController {
|
||||
constructor(private readonly actorService: ActorService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateActorDto) {
|
||||
return this.actorService.create(dto);
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ActorService } from './actor.service';
|
||||
import { ActorController } from './actor.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ActorController],
|
||||
providers: [ActorService],
|
||||
})
|
||||
export class ActorModule {}
|
||||
@ -1,18 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Actor } from 'generated/prisma';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateActorDto } from './dto/create-actor.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ActorService {
|
||||
constructor(private prismaService: PrismaService) {}
|
||||
|
||||
async create(dto: CreateActorDto): Promise<Actor> {
|
||||
const { name } = dto;
|
||||
return this.prismaService.actor.create({
|
||||
data: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
export class CreateActorDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@MaxLength(64)
|
||||
name: string;
|
||||
}
|
||||
@ -1,26 +1,4 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
UseGuards,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { UserAgentDecorator } from './common/decorators/user-agent.decorator';
|
||||
import { AuthGuard } from './common/guards/auth.guard';
|
||||
import { StringToLowercasePipe } from './common/pipes/string-to-lowercase.pipe';
|
||||
import { Controller } from '@nestjs/common';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
@UsePipes(StringToLowercasePipe)
|
||||
@Post()
|
||||
create(@Body('title') title: string) {
|
||||
return `Movie ${title}`;
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Get()
|
||||
getProfile(@UserAgentDecorator() userAgent: string) {
|
||||
return { name: 'FirstName', age: 22, userAgent };
|
||||
}
|
||||
}
|
||||
export class AppController {}
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AppController } from './app.controller';
|
||||
import { TaskModule } from './task/task.module';
|
||||
import { MovieModule } from './movie/movie.module';
|
||||
import { ReviewModule } from './review/review.module';
|
||||
import { ActorModule } from './actor/actor.module';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -13,10 +10,7 @@ import { PrismaModule } from './prisma/prisma.module';
|
||||
isGlobal: true,
|
||||
}),
|
||||
PrismaModule,
|
||||
TaskModule,
|
||||
MovieModule,
|
||||
ReviewModule,
|
||||
ActorModule,
|
||||
AuthModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [],
|
||||
|
||||
92
src/auth/auth.controller.ts
Normal file
92
src/auth/auth.controller.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBadRequestResponse,
|
||||
ApiConflictResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiUnauthorizedResponse,
|
||||
} from '@nestjs/swagger';
|
||||
import { AuthService } from './auth.service';
|
||||
import { Authorization } from './decorators/authorixation.decorator';
|
||||
import { Authorized } from './decorators/authorized.decorator';
|
||||
import { AuthResponse } from './dto/auth.dto';
|
||||
import { LoginRequest } from './dto/login.dto';
|
||||
import { RegisterRequest } from './dto/register.dto';
|
||||
import type { Request, Response } from 'express';
|
||||
import { User } from 'generated/prisma';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Register user',
|
||||
description: 'Register user by email and password',
|
||||
})
|
||||
@ApiBadRequestResponse({ description: 'Validation error' })
|
||||
@ApiOkResponse({ type: AuthResponse })
|
||||
@ApiConflictResponse({
|
||||
example: `User with email john-doe@example.com already exist`,
|
||||
})
|
||||
@Post('register')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async register(
|
||||
@Res({ passthrough: true }) response: Response,
|
||||
@Body() dto: RegisterRequest,
|
||||
) {
|
||||
return this.authService.register(response, dto);
|
||||
}
|
||||
|
||||
@ApiBadRequestResponse({ description: 'Validation error' })
|
||||
@ApiOkResponse({ type: AuthResponse })
|
||||
@ApiNotFoundResponse({ description: 'User not found' })
|
||||
@ApiOperation({
|
||||
summary: 'Login user',
|
||||
description: 'Login via email and password',
|
||||
})
|
||||
@Post('login')
|
||||
async login(
|
||||
@Res({ passthrough: true }) response: Response,
|
||||
@Body() dto: LoginRequest,
|
||||
) {
|
||||
return this.authService.login(response, dto);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Logout user',
|
||||
})
|
||||
@Post('logout')
|
||||
logout(@Res({ passthrough: true }) response: Response) {
|
||||
return this.authService.logout(response);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Update user refresh token',
|
||||
})
|
||||
@ApiOkResponse({ type: AuthResponse })
|
||||
@ApiUnauthorizedResponse({ description: 'User should auth' })
|
||||
@Post('refresh')
|
||||
async refresh(
|
||||
@Req() request: Request,
|
||||
@Res({ passthrough: true }) response: Response,
|
||||
) {
|
||||
return this.authService.refresh(request, response);
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Get('me')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
me(@Authorized('id') user: User['id']) {
|
||||
return { id: user };
|
||||
}
|
||||
}
|
||||
22
src/auth/auth.module.ts
Normal file
22
src/auth/auth.module.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { getJwtConfig } from '../config/jwt.config';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PassportModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: getJwtConfig,
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
})
|
||||
export class AuthModule {}
|
||||
165
src/auth/auth.service.ts
Normal file
165
src/auth/auth.service.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { hash, verify } from 'argon2';
|
||||
import type { Request, Response } from 'express';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { isDev } from '../utils/is-dev';
|
||||
import { LoginRequest } from './dto/login.dto';
|
||||
import { RegisterRequest } from './dto/register.dto';
|
||||
import { User } from 'generated/prisma';
|
||||
import { JwtPayload } from './interfaces/jwt.interface';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly COOKIE_DOMAIN: string;
|
||||
private readonly JWT_ACCESS_TOKEN_TTL: string;
|
||||
private readonly JWT_REFRESH_TOKEN_TTL: string;
|
||||
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly jwtService: JwtService,
|
||||
) {
|
||||
this.COOKIE_DOMAIN = this.configService.getOrThrow('COOKIE_DOMAIN');
|
||||
this.JWT_ACCESS_TOKEN_TTL = this.configService.getOrThrow(
|
||||
'JWT_ACCESS_TOKEN_TTL',
|
||||
);
|
||||
this.JWT_REFRESH_TOKEN_TTL = this.configService.getOrThrow(
|
||||
'JWT_REFRESH_TOKEN_TTL',
|
||||
);
|
||||
}
|
||||
|
||||
async register(res: Response, dto: RegisterRequest) {
|
||||
const { email, name, password } = dto;
|
||||
|
||||
const existUser = await this.prismaService.user.findUnique({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
if (existUser) {
|
||||
throw new ConflictException(`User with email ${email} already exist`);
|
||||
}
|
||||
|
||||
const user = await this.prismaService.user.create({
|
||||
data: {
|
||||
email,
|
||||
name,
|
||||
password: await hash(password),
|
||||
},
|
||||
});
|
||||
|
||||
return this.auth(res, user.id);
|
||||
}
|
||||
|
||||
async login(res: Response, dto: LoginRequest) {
|
||||
const { email, password } = dto;
|
||||
|
||||
const user = await this.prismaService.user.findUnique({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
password: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ConflictException('Wrong email or password');
|
||||
}
|
||||
|
||||
const isValidPassword = await verify(user.password, password);
|
||||
if (!isValidPassword) {
|
||||
throw new ConflictException('Wrong email or password');
|
||||
}
|
||||
|
||||
return this.auth(res, user.id);
|
||||
}
|
||||
|
||||
logout(res: Response) {
|
||||
this.setCookie(res, 'refreshToken', new Date(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
auth(res: Response, id: User['id']) {
|
||||
const { accessToken, refreshToken } = this.generateTokens(id);
|
||||
|
||||
this.setCookie(res, refreshToken, new Date(Date.now() + 60 * 60 * 24 * 7));
|
||||
|
||||
return { accessToken };
|
||||
}
|
||||
|
||||
async validate(id: string) {
|
||||
const user = await this.prismaService.user.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public async refresh(req: Request, res: Response) {
|
||||
const refreshToken = req.cookies['refreshToken'] as string;
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
const payload: JwtPayload = await this.jwtService.verifyAsync(refreshToken);
|
||||
if (payload) {
|
||||
const user = await this.prismaService.user.findUnique({
|
||||
where: {
|
||||
id: payload.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
this.auth(res, user.id);
|
||||
}
|
||||
}
|
||||
|
||||
private generateTokens(id: User['id']) {
|
||||
const payload: JwtPayload = { id };
|
||||
|
||||
const accessToken = this.jwtService.sign(payload, {
|
||||
expiresIn: this.JWT_ACCESS_TOKEN_TTL,
|
||||
});
|
||||
const refreshToken = this.jwtService.sign(payload, {
|
||||
expiresIn: this.JWT_REFRESH_TOKEN_TTL,
|
||||
});
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private setCookie(res: Response, value: string, expires: Date) {
|
||||
res.cookie('refreshToken', value, {
|
||||
httpOnly: true,
|
||||
domain: this.COOKIE_DOMAIN,
|
||||
expires,
|
||||
secure: !isDev(this.configService),
|
||||
sameSite: isDev(this.configService) ? 'none' : 'lax',
|
||||
});
|
||||
}
|
||||
}
|
||||
6
src/auth/decorators/authorixation.decorator.ts
Normal file
6
src/auth/decorators/authorixation.decorator.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { applyDecorators, UseGuards } from '@nestjs/common';
|
||||
import { JwtGuard } from '../guards/auth.guard';
|
||||
|
||||
export function Authorization() {
|
||||
return applyDecorators(UseGuards(JwtGuard));
|
||||
}
|
||||
13
src/auth/decorators/authorized.decorator.ts
Normal file
13
src/auth/decorators/authorized.decorator.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { User } from 'generated/prisma';
|
||||
|
||||
export const Authorized = createParamDecorator(
|
||||
(data: keyof User, ctx: ExecutionContext) => {
|
||||
const req: Request = ctx.switchToHttp().getRequest();
|
||||
|
||||
const user = req.user;
|
||||
|
||||
return data ? user![data] : user;
|
||||
},
|
||||
);
|
||||
12
src/auth/dto/auth.dto.ts
Normal file
12
src/auth/dto/auth.dto.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class AuthResponse {
|
||||
@ApiProperty({
|
||||
title: 'JWT Refresh Token',
|
||||
example:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' +
|
||||
'eyJpZCI6Ijk4NGFlODk3LWY2ZjMtNGZhNC1iMjYxLWExOWU5NDY3OWVmMCIsImlhdCI6MTc0OTg5NTc0MywiZXhwIjoxNzQ5OTAyOTQzfQ.' +
|
||||
'SkW6SW9wcTy2Vc9NPVbg5Q6RExscH4Mgth5HerBRGrs',
|
||||
})
|
||||
accessToken: string;
|
||||
}
|
||||
33
src/auth/dto/login.dto.ts
Normal file
33
src/auth/dto/login.dto.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsEmail,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class LoginRequest {
|
||||
@ApiProperty({
|
||||
description: 'User email',
|
||||
example: 'john-doe@example.com',
|
||||
})
|
||||
@IsString({ message: 'Email should be a string' })
|
||||
@IsNotEmpty({ message: 'Email is required' })
|
||||
@IsEmail({}, { message: 'Not valid email' })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'User password',
|
||||
example: '1q2w3e4r',
|
||||
minLength: 6,
|
||||
maxLength: 128,
|
||||
})
|
||||
@IsString({ message: 'Password should be a string' })
|
||||
@IsNotEmpty({ message: 'Password is required' })
|
||||
@MinLength(6, { message: 'Password should contain more than 6 characters' })
|
||||
@MaxLength(128, {
|
||||
message: 'Password should contain less than 128 characters',
|
||||
})
|
||||
password: string;
|
||||
}
|
||||
43
src/auth/dto/register.dto.ts
Normal file
43
src/auth/dto/register.dto.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsEmail,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class RegisterRequest {
|
||||
@ApiProperty({
|
||||
description: 'User name',
|
||||
example: 'John Doe',
|
||||
maxLength: 50,
|
||||
})
|
||||
@IsString({ message: 'Name should be a string' })
|
||||
@IsNotEmpty({ message: 'Name is required' })
|
||||
@MaxLength(50, { message: "Name's length should be less 50 chars" })
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'User email',
|
||||
example: 'john-doe@example.com',
|
||||
})
|
||||
@IsString({ message: 'Email should be a string' })
|
||||
@IsNotEmpty({ message: 'Email is required' })
|
||||
@IsEmail({}, { message: 'Not valid email' })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'User password',
|
||||
example: '1q2w3e4r',
|
||||
minLength: 6,
|
||||
maxLength: 128,
|
||||
})
|
||||
@IsString({ message: 'Password should be a string' })
|
||||
@IsNotEmpty({ message: 'Password is required' })
|
||||
@MinLength(6, { message: 'Password should contain more than 6 characters' })
|
||||
@MaxLength(128, {
|
||||
message: 'Password should contain less than 128 characters',
|
||||
})
|
||||
password: string;
|
||||
}
|
||||
3
src/auth/guards/auth.guard.ts
Normal file
3
src/auth/guards/auth.guard.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
export class JwtGuard extends AuthGuard('jwt') {}
|
||||
5
src/auth/interfaces/jwt.interface.ts
Normal file
5
src/auth/interfaces/jwt.interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { User } from 'generated/prisma';
|
||||
|
||||
export interface JwtPayload {
|
||||
id: User['id'];
|
||||
}
|
||||
25
src/auth/strategies/jwt.strategy.ts
Normal file
25
src/auth/strategies/jwt.strategy.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { JwtPayload } from '../interfaces/jwt.interface';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.getOrThrow('JWT_SECRET'),
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload) {
|
||||
return await this.authService.validate(payload.id);
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
|
||||
export const UserAgentDecorator = createParamDecorator(
|
||||
(_data: unknown, context: ExecutionContext) => {
|
||||
const request: Request = context.switchToHttp().getRequest();
|
||||
return request.headers['user-agent'];
|
||||
},
|
||||
);
|
||||
@ -1,34 +0,0 @@
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger(AllExceptionsFilter.name);
|
||||
catch(exception: any, host: ArgumentsHost): any {
|
||||
const context = host.switchToHttp();
|
||||
const response: Response = context.getResponse();
|
||||
const request: Request = context.getRequest();
|
||||
|
||||
const status =
|
||||
exception instanceof HttpException ? exception.getStatus() : 500;
|
||||
const message =
|
||||
exception instanceof HttpException
|
||||
? exception.message
|
||||
: 'Internal server error';
|
||||
|
||||
this.logger.error(message, exception);
|
||||
|
||||
response.status(status).json({
|
||||
status,
|
||||
message,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.baseUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const request: Request = context.switchToHttp().getRequest();
|
||||
const token = request.headers.authorization;
|
||||
|
||||
if (!token || !token.startsWith('Bearer')) {
|
||||
throw new UnauthorizedException('You are not authorized');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { map, Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class ResponseInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler<any>,
|
||||
): Observable<any> {
|
||||
return next.handle().pipe(
|
||||
map((data) => ({
|
||||
status: 'OK',
|
||||
data,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: NextFunction) {
|
||||
console.log(`Request ${req.method} ${req.baseUrl}`);
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
export function loggerMiddlewareFunc(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) {
|
||||
console.log(`[${req.method}]: ${req.originalUrl}`);
|
||||
next();
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class StringToLowercasePipe implements PipeTransform {
|
||||
transform(value: any, metadata: ArgumentMetadata): any {
|
||||
if (typeof value === 'string') {
|
||||
return value.toLocaleLowerCase();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
17
src/config/jwt.config.ts
Normal file
17
src/config/jwt.config.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtModuleOptions } from '@nestjs/jwt';
|
||||
|
||||
export async function getJwtConfig(
|
||||
configService: ConfigService,
|
||||
): Promise<JwtModuleOptions> {
|
||||
return {
|
||||
secret: configService.getOrThrow('JWT_SECRET'),
|
||||
signOptions: {
|
||||
algorithm: 'HS256',
|
||||
},
|
||||
verifyOptions: {
|
||||
algorithms: ['HS256'],
|
||||
ignoreExpiration: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
11
src/config/swagger.config.ts
Normal file
11
src/config/swagger.config.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { DocumentBuilder } from '@nestjs/swagger';
|
||||
|
||||
export function getSwaggerConfig() {
|
||||
return new DocumentBuilder()
|
||||
.setTitle('Nest.js Course')
|
||||
.setDescription('Documentation for nest.js')
|
||||
.setVersion('0.0.1')
|
||||
.setContact('Krylov Sergey', 'https://ksv741.tech', 'ksv741@mail.ru')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
}
|
||||
30
src/main.ts
30
src/main.ts
@ -1,30 +1,16 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { AppModule } from './app.module';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
|
||||
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
|
||||
import { loggerMiddlewareFunc } from './common/middlewares/logger/logger.middleware';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import { setupSwagger } from './utils/swagger';
|
||||
|
||||
async function bootstrap() {
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Nest.js Course')
|
||||
.setDescription('Documentation for nest.js')
|
||||
.setVersion('0.0.1')
|
||||
.setContact('Krylov Sergey', 'https://ksv741.tech', 'ksv741@mail.ru')
|
||||
.setLicense('MIT', 'https://github.com')
|
||||
.build();
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('/docs', app, document, {
|
||||
jsonDocumentUrl: 'docs/swagger.json',
|
||||
yamlDocumentUrl: 'docs/swagger.yaml',
|
||||
customSiteTitle: 'Nest.js base course',
|
||||
});
|
||||
|
||||
setupSwagger(app);
|
||||
app.use(cookieParser());
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
app.use(loggerMiddlewareFunc);
|
||||
app.useGlobalInterceptors(new ResponseInterceptor());
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsArray,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
IsUrl,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { Actor } from 'generated/prisma';
|
||||
|
||||
export class MovieDto {
|
||||
@ApiProperty({
|
||||
description: 'Название фильма',
|
||||
example: 'Fight Club',
|
||||
type: String,
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
title: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Год выпуска',
|
||||
example: 1995,
|
||||
type: Number,
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsInt()
|
||||
@Min(1888)
|
||||
@Max(new Date().getFullYear())
|
||||
releaseYear: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Список актеров',
|
||||
example: ['1234', '5678'],
|
||||
type: [String],
|
||||
})
|
||||
@IsArray()
|
||||
@IsUUID('4', { each: true })
|
||||
actorIds: Actor['id'][];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'URL постера',
|
||||
example: 'https://storage.example.com/posters/12345.jpg',
|
||||
})
|
||||
@IsUrl()
|
||||
imageUrl: string;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class MovieResponse {
|
||||
@ApiProperty({
|
||||
description: 'Id фильма',
|
||||
example: '123456',
|
||||
type: String,
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Название фильма',
|
||||
example: 'Fight Club',
|
||||
type: String,
|
||||
})
|
||||
title: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Год выпуска',
|
||||
example: 1995,
|
||||
type: Number,
|
||||
})
|
||||
releaseYear: number;
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiNotFoundResponse,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { MovieDto } from './dto/movie.dto';
|
||||
import { MovieResponse } from './dto/response.dto';
|
||||
import { MovieService } from './movie.service';
|
||||
import { Movie } from 'generated/prisma';
|
||||
|
||||
@ApiTags('Movies')
|
||||
@Controller('movies')
|
||||
export class MovieController {
|
||||
constructor(private readonly movieService: MovieService) {}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Список фильмов',
|
||||
description: 'Получить список всех фильмов',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
description: 'Фильмы найдены',
|
||||
})
|
||||
@Get()
|
||||
public findAll() {
|
||||
return this.movieService.findAll();
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Поллучить фильм',
|
||||
description: 'Получить фильм по id',
|
||||
})
|
||||
@ApiParam({ name: 'id', type: 'string', description: 'ID фильма' })
|
||||
@ApiOkResponse({ description: 'Фильм найден', type: MovieResponse })
|
||||
@ApiNotFoundResponse({
|
||||
description: 'Филь не найден',
|
||||
example: {
|
||||
status: 404,
|
||||
message: 'Not found movie with id 22',
|
||||
timestamp: '2025-06-12T10:46:30.694Z',
|
||||
path: '',
|
||||
},
|
||||
})
|
||||
@Get(':id')
|
||||
public findById(@Param('id') id: Movie['id']) {
|
||||
return this.movieService.findById(id);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Добавить фильм',
|
||||
description: 'Добавить фильм в коллекцию',
|
||||
})
|
||||
@Post()
|
||||
public create(@Body() dto: MovieDto) {
|
||||
return this.movieService.create(dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
public update(@Body() dto: MovieDto, @Param('id') id: Movie['id']) {
|
||||
return this.movieService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
public delete(@Param('id') id: Movie['id']) {
|
||||
return this.movieService.delete(id);
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MovieService } from './movie.service';
|
||||
import { MovieController } from './movie.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [MovieController],
|
||||
providers: [MovieService],
|
||||
})
|
||||
export class MovieModule {}
|
||||
@ -1,110 +0,0 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { MovieDto } from './dto/movie.dto';
|
||||
import { Movie } from 'generated/prisma';
|
||||
|
||||
@Injectable()
|
||||
export class MovieService {
|
||||
constructor(private prismaService: PrismaService) {}
|
||||
|
||||
async findAll() {
|
||||
return this.prismaService.movie.findMany({
|
||||
orderBy: {
|
||||
id: 'asc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
actors: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id: Movie['id']): Promise<Movie> {
|
||||
const movie = await this.prismaService.movie.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
actors: true,
|
||||
poster: true,
|
||||
reviews: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!movie) {
|
||||
throw new NotFoundException(`Not found movie with id ${id}`);
|
||||
}
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
async create(dto: MovieDto) {
|
||||
const { actorIds, releaseYear, title, imageUrl } = dto;
|
||||
const actors = await this.prismaService.actor.findMany({
|
||||
where: {
|
||||
id: { in: actorIds },
|
||||
},
|
||||
});
|
||||
if (!actors || actors.length === 0) {
|
||||
throw new NotFoundException('Not found actors');
|
||||
}
|
||||
|
||||
return this.prismaService.movie.create({
|
||||
data: {
|
||||
title,
|
||||
releaseYear,
|
||||
actors: {
|
||||
connect: actors.map((actor) => ({
|
||||
id: actor.id,
|
||||
})),
|
||||
},
|
||||
poster: {
|
||||
create: {
|
||||
url: imageUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: Movie['id'], dto: MovieDto): Promise<boolean> {
|
||||
const movie = await this.findById(id);
|
||||
const actors = await this.prismaService.actor.findMany({
|
||||
where: {
|
||||
id: { in: dto.actorIds },
|
||||
},
|
||||
});
|
||||
await this.prismaService.movie.update({
|
||||
where: { id: movie.id },
|
||||
data: {
|
||||
title: dto.title,
|
||||
releaseYear: dto.releaseYear,
|
||||
poster: {
|
||||
create: {
|
||||
url: dto.imageUrl,
|
||||
},
|
||||
},
|
||||
actors: {
|
||||
connect: actors.map((actor) => ({ id: actor.id })),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async delete(id: Movie['id']): Promise<Movie> {
|
||||
const movie = await this.findById(id);
|
||||
await this.prismaService.movie.delete({
|
||||
where: {
|
||||
id: movie.id,
|
||||
},
|
||||
});
|
||||
|
||||
return movie;
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { IsNotEmpty, IsString, IsUUID, Max, Min } from 'class-validator';
|
||||
import { Review } from 'generated/prisma';
|
||||
|
||||
export class CreateReviewDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
text: Review['text'];
|
||||
|
||||
@IsNotEmpty()
|
||||
@Min(0)
|
||||
@Max(10)
|
||||
rating: Review['rating'];
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
movieId: Review['movieId'];
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||
import { CreateReviewDto } from './dto/create-review.dto';
|
||||
import { ReviewService } from './review.service';
|
||||
|
||||
@Controller('reviews')
|
||||
export class ReviewController {
|
||||
constructor(private readonly reviewService: ReviewService) {}
|
||||
|
||||
@Post()
|
||||
public async create(@Body() dto: CreateReviewDto) {
|
||||
return this.reviewService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
public findAll() {
|
||||
return this.reviewService.findAll();
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MovieService } from '../movie/movie.service';
|
||||
import { ReviewService } from './review.service';
|
||||
import { ReviewController } from './review.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ReviewController],
|
||||
providers: [ReviewService, MovieService],
|
||||
})
|
||||
export class ReviewModule {}
|
||||
@ -1,29 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Review } from 'generated/prisma';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { CreateReviewDto } from './dto/create-review.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ReviewService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
async findAll(): Promise<Review[]> {
|
||||
return this.prismaService.review.findMany();
|
||||
}
|
||||
|
||||
async create(dto: CreateReviewDto): Promise<Review> {
|
||||
const { movieId, rating, text } = dto;
|
||||
|
||||
return this.prismaService.review.create({
|
||||
data: {
|
||||
text,
|
||||
rating,
|
||||
movie: {
|
||||
connect: {
|
||||
id: movieId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { registerDecorator, ValidatorOptions } from 'class-validator';
|
||||
|
||||
export function StartsWiths(
|
||||
prefix: string,
|
||||
validatorOption?: ValidatorOptions,
|
||||
) {
|
||||
return (obj: object, propertyName: string) => {
|
||||
registerDecorator({
|
||||
name: 'startsWith',
|
||||
target: obj.constructor,
|
||||
propertyName,
|
||||
options: validatorOption,
|
||||
validator: {
|
||||
validate(value: any): Promise<boolean> | boolean {
|
||||
console.log('value', value);
|
||||
return typeof value === 'string' && value.startsWith(prefix);
|
||||
},
|
||||
defaultMessage(): string {
|
||||
return `Should start with "${prefix}"`;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsString,
|
||||
Length,
|
||||
} from 'class-validator';
|
||||
import { StartsWiths } from '../docorators/start-with.decorator';
|
||||
|
||||
export enum TaskTag {
|
||||
WORK = 'work',
|
||||
STUDY = 'study',
|
||||
HOME = 'home',
|
||||
}
|
||||
|
||||
export class CreateTaskDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@StartsWiths('task')
|
||||
@Length(2, 10)
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description: string;
|
||||
|
||||
@IsInt({})
|
||||
@IsOptional()
|
||||
@IsPositive()
|
||||
priority: number;
|
||||
|
||||
@IsArray()
|
||||
@IsEnum(TaskTag, { each: true })
|
||||
@IsOptional()
|
||||
tags: TaskTag[];
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import { IsBoolean, IsNotEmpty, IsString, Length } from 'class-validator';
|
||||
|
||||
export class UpdateTaskDto {
|
||||
@IsString({ message: 'Должно быть строкой' })
|
||||
@IsNotEmpty({ message: 'Не должно быть пустым' })
|
||||
@Length(2, 40, { message: 'Должно быть от 2 до 40 символов' })
|
||||
title: string;
|
||||
|
||||
@IsBoolean({ message: 'Должно быть булевым выражением' })
|
||||
isCompleted: boolean;
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { CreateTaskDto } from './dto/create-task.dto';
|
||||
import { UpdateTaskDto } from './dto/update-task.dto';
|
||||
import { TaskService } from './task.service';
|
||||
|
||||
@Controller('task')
|
||||
export class TaskController {
|
||||
constructor(private readonly taskService: TaskService) {}
|
||||
|
||||
@Get('all')
|
||||
findAll() {
|
||||
return this.taskService.findAll();
|
||||
}
|
||||
|
||||
@Get('by-id/:id')
|
||||
findById(
|
||||
@Param('id')
|
||||
id: string,
|
||||
) {
|
||||
return this.taskService.findById(Number(id));
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateTaskDto) {
|
||||
return this.taskService.create(dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateTaskDto) {
|
||||
return this.taskService.update(Number(id), dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
patchUpdate(@Param('id') id: string, @Body() dto: Partial<UpdateTaskDto>) {
|
||||
return this.taskService.patchTask(Number(id), dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
delete(@Param('id') id: string) {
|
||||
return this.taskService.delete(Number(id));
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TaskService } from './task.service';
|
||||
import { TaskController } from './task.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [TaskController],
|
||||
providers: [TaskService],
|
||||
})
|
||||
export class TaskModule {}
|
||||
@ -1,73 +0,0 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateTaskDto } from './dto/create-task.dto';
|
||||
import { UpdateTaskDto } from './dto/update-task.dto';
|
||||
|
||||
@Injectable()
|
||||
export class TaskService {
|
||||
private tasks = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Learn Nestjs',
|
||||
isCompleted: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Build API',
|
||||
isCompleted: true,
|
||||
},
|
||||
];
|
||||
|
||||
findAll() {
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
findById(id: number) {
|
||||
const task = this.tasks.find((task) => task.id === id);
|
||||
|
||||
if (!task) {
|
||||
throw new NotFoundException(`Not found task with id ${id}`);
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
create(dto: CreateTaskDto) {
|
||||
const { title, description, priority, tags } = dto;
|
||||
const newTask = {
|
||||
id: this.tasks.length + 1,
|
||||
title,
|
||||
isCompleted: false,
|
||||
description,
|
||||
priority,
|
||||
tags,
|
||||
};
|
||||
|
||||
this.tasks.push(newTask);
|
||||
|
||||
return newTask;
|
||||
}
|
||||
|
||||
update(id: number, dto: UpdateTaskDto) {
|
||||
const task = this.findById(id);
|
||||
const { isCompleted, title } = dto;
|
||||
|
||||
task.isCompleted = isCompleted;
|
||||
task.title = title;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
patchTask(id: number, dto: Partial<UpdateTaskDto>) {
|
||||
const task = this.findById(id);
|
||||
|
||||
Object.assign(task, dto);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
delete(id: number) {
|
||||
const task = this.findById(id);
|
||||
this.tasks = this.tasks.filter((t) => t !== task);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
5
src/utils/is-dev.ts
Normal file
5
src/utils/is-dev.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
export const isDev = (configService: ConfigService) => {
|
||||
return configService.getOrThrow('NODE_ENV') === 'development';
|
||||
};
|
||||
14
src/utils/swagger.ts
Normal file
14
src/utils/swagger.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { SwaggerModule } from '@nestjs/swagger';
|
||||
import { getSwaggerConfig } from '../config/swagger.config';
|
||||
|
||||
export function setupSwagger(app: INestApplication) {
|
||||
const config = getSwaggerConfig();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('/docs', app, document, {
|
||||
jsonDocumentUrl: 'docs/swagger.json',
|
||||
yamlDocumentUrl: 'docs/swagger.yaml',
|
||||
customSiteTitle: 'Nest.js base course',
|
||||
});
|
||||
}
|
||||
225
yarn.lock
225
yarn.lock
@ -1022,11 +1022,24 @@
|
||||
path-to-regexp "8.2.0"
|
||||
tslib "2.8.1"
|
||||
|
||||
"@nestjs/jwt@^11.0.0":
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-11.0.0.tgz#aef1590e70830c70fba0f59e9b17314dc4d36822"
|
||||
integrity sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==
|
||||
dependencies:
|
||||
"@types/jsonwebtoken" "9.0.7"
|
||||
jsonwebtoken "9.0.2"
|
||||
|
||||
"@nestjs/mapped-types@*", "@nestjs/mapped-types@2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19"
|
||||
integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==
|
||||
|
||||
"@nestjs/passport@^11.0.5":
|
||||
version "11.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-11.0.5.tgz#dd3e506c2fb7ddc80fd1321c01cc1a0ca6d6b609"
|
||||
integrity sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==
|
||||
|
||||
"@nestjs/platform-express@^11.0.1":
|
||||
version "11.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-11.1.2.tgz#e4b2671c74876ff399f50b7a7f36cb4b4c9e35ec"
|
||||
@ -1108,6 +1121,11 @@
|
||||
dependencies:
|
||||
"@noble/hashes" "^1.1.5"
|
||||
|
||||
"@phc/format@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@phc/format/-/format-1.0.0.tgz#b5627003b3216dc4362125b13f48a4daa76680e4"
|
||||
integrity sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==
|
||||
|
||||
"@pkgr/core@^0.2.4":
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.7.tgz#eb5014dfd0b03e7f3ba2eeeff506eed89b028058"
|
||||
@ -1380,6 +1398,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cookie-parser@^1.4.9":
|
||||
version "1.4.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.9.tgz#f0e79c766a58ee7369a52e7509b3840222f68ed2"
|
||||
integrity sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==
|
||||
|
||||
"@types/cookiejar@^2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
|
||||
@ -1416,6 +1439,15 @@
|
||||
"@types/range-parser" "*"
|
||||
"@types/send" "*"
|
||||
|
||||
"@types/express@*":
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956"
|
||||
integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "^5.0.0"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/express@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.2.tgz#7be9e337a5745d6b43ef5b0c352dad94a7f0c256"
|
||||
@ -1474,6 +1506,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||
|
||||
"@types/jsonwebtoken@*":
|
||||
version "9.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz#a4c3a446c0ebaaf467a58398382616f416345fb3"
|
||||
integrity sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==
|
||||
dependencies:
|
||||
"@types/ms" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/jsonwebtoken@9.0.7":
|
||||
version "9.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2"
|
||||
integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/methods@^1.1.4":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547"
|
||||
@ -1484,6 +1531,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
|
||||
integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
|
||||
|
||||
"@types/ms@*":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
|
||||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||
|
||||
"@types/node@*", "@types/node@^22.10.7":
|
||||
version "22.15.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.29.tgz#c75999124a8224a3f79dd8b6ccfb37d74098f678"
|
||||
@ -1491,6 +1543,29 @@
|
||||
dependencies:
|
||||
undici-types "~6.21.0"
|
||||
|
||||
"@types/passport-jwt@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-4.0.1.tgz#080fbe934fb9f6954fb88ec4cdf4bb2cc7c4d435"
|
||||
integrity sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==
|
||||
dependencies:
|
||||
"@types/jsonwebtoken" "*"
|
||||
"@types/passport-strategy" "*"
|
||||
|
||||
"@types/passport-strategy@*":
|
||||
version "0.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.38.tgz#482abba0b165cd4553ec8b748f30b022bd6c04d3"
|
||||
integrity sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
"@types/passport" "*"
|
||||
|
||||
"@types/passport@*", "@types/passport@^1.0.17":
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.17.tgz#718a8d1f7000ebcf6bbc0853da1bc8c4bc7ea5e6"
|
||||
integrity sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5"
|
||||
@ -2022,6 +2097,15 @@ arg@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||
|
||||
argon2@^0.43.0:
|
||||
version "0.43.0"
|
||||
resolved "https://registry.yarnpkg.com/argon2/-/argon2-0.43.0.tgz#4a5d8943506923ce7450f4e9069e03e134c7473e"
|
||||
integrity sha512-u/HKLcbWShVDhkfwI4hWyiUf3qyX8QhTfaIv2cWE18uqhXCmR5hb6Ed7oqYi2KCQegeAnRhiFzbjzm7i5yl1GA==
|
||||
dependencies:
|
||||
"@phc/format" "^1.0.0"
|
||||
node-addon-api "^8.3.1"
|
||||
node-gyp-build "^4.8.4"
|
||||
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
@ -2229,6 +2313,11 @@ buffer-crc32@~0.2.3:
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-equal-constant-time@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
@ -2511,12 +2600,25 @@ convert-source-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
cookie-parser@^1.4.7:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26"
|
||||
integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==
|
||||
dependencies:
|
||||
cookie "0.7.2"
|
||||
cookie-signature "1.0.6"
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||
|
||||
cookie-signature@^1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
|
||||
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
|
||||
|
||||
cookie@^0.7.1:
|
||||
cookie@0.7.2, cookie@^0.7.1:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
||||
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
||||
@ -2686,6 +2788,13 @@ eastasianwidth@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
@ -4141,6 +4250,39 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonwebtoken@9.0.2, jsonwebtoken@^9.0.0:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
|
||||
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^7.5.4"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9"
|
||||
integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "^1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
keyv@^4.5.3, keyv@^4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||
@ -4205,6 +4347,36 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
@ -4215,6 +4387,11 @@ lodash.merge@^4.6.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
lodash@4.17.21, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
@ -4410,7 +4587,7 @@ mkdirp@^0.5.4:
|
||||
dependencies:
|
||||
minimist "^1.2.6"
|
||||
|
||||
ms@^2.1.3:
|
||||
ms@^2.1.1, ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
@ -4453,6 +4630,11 @@ node-abort-controller@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
|
||||
|
||||
node-addon-api@^8.3.1:
|
||||
version "8.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.4.0.tgz#8cbc68ee1c216368921a8f63038a23a39cd8ba44"
|
||||
integrity sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==
|
||||
|
||||
node-emoji@1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
|
||||
@ -4460,6 +4642,11 @@ node-emoji@1.11.0:
|
||||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
|
||||
node-gyp-build@^4.8.4:
|
||||
version "4.8.4"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
|
||||
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
@ -4615,6 +4802,28 @@ parseurl@^1.3.3:
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
passport-jwt@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d"
|
||||
integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==
|
||||
dependencies:
|
||||
jsonwebtoken "^9.0.0"
|
||||
passport-strategy "^1.0.0"
|
||||
|
||||
passport-strategy@1.x.x, passport-strategy@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
|
||||
integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
|
||||
|
||||
passport@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/passport/-/passport-0.7.0.tgz#3688415a59a48cf8068417a8a8092d4492ca3a05"
|
||||
integrity sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==
|
||||
dependencies:
|
||||
passport-strategy "1.x.x"
|
||||
pause "0.0.1"
|
||||
utils-merge "^1.0.1"
|
||||
|
||||
path-exists@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||
@ -4653,6 +4862,11 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
pause@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
|
||||
integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
|
||||
|
||||
peek-readable@^5.3.1:
|
||||
version "5.4.2"
|
||||
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.4.2.tgz#aff1e1ba27a7d6911ddb103f35252ffc1787af49"
|
||||
@ -4951,7 +5165,7 @@ rxjs@^7.8.1:
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
|
||||
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
@ -5653,6 +5867,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
utils-merge@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
|
||||
v8-compile-cache-lib@^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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user