From 1988673f8011cbbcad5cc2ca08a5e7060b73db85 Mon Sep 17 00:00:00 2001 From: M1000fr Date: Mon, 2 Dec 2024 13:57:47 +0100 Subject: [PATCH] feat: add ConfigService with Env validator - add Role decorator --- .env.example | 12 ++- package.json | 2 + prisma/schema.prisma | 2 +- src/app.module.ts | 23 ++++-- src/app.service.ts | 10 --- src/auth/Strategy/discord.strategy.ts | 33 -------- src/auth/Strategy/jwt.strategy.ts | 19 ----- src/config/env.ts | 17 ++++ src/main.ts | 8 +- .../auth/Decorators/roles.decorator.ts | 4 + .../auth/Guards/discord.guard.ts | 0 src/{ => modules}/auth/Guards/jwt.guard.ts | 0 src/modules/auth/Guards/role.guard.ts | 39 ++++++++++ src/modules/auth/Strategy/discord.strategy.ts | 37 +++++++++ src/modules/auth/Strategy/jwt.strategy.ts | 30 ++++++++ src/{ => modules}/auth/auth.controller.ts | 18 ++--- src/{ => modules}/auth/auth.module.ts | 2 + src/{ => modules}/auth/auth.service.ts | 0 src/{ => modules}/prisma/prisma.service.ts | 0 src/modules/user/dto/bulk-delete-user.dto.ts | 9 +++ src/modules/user/dto/create-user.dto.ts | 6 ++ .../user/dto/update-user.dto.ts} | 4 +- src/modules/user/user.controller.ts | 52 +++++++++++++ src/modules/user/user.entity.ts | 17 ++++ src/{ => modules}/user/user.module.ts | 5 +- src/modules/user/user.service.ts | 56 ++++++++++++++ src/user/dto/create-user.input.ts | 9 --- src/user/dto/setpassword-user.input.ts | 9 --- src/user/user.controller.ts | 34 -------- src/user/user.entity.ts | 12 --- src/user/user.service.ts | 77 ------------------- src/validations/env.validation.ts | 17 ++++ tsconfig.json | 9 ++- yarn.lock | 63 +++++++++++++++ 34 files changed, 404 insertions(+), 231 deletions(-) delete mode 100644 src/app.service.ts delete mode 100644 src/auth/Strategy/discord.strategy.ts delete mode 100644 src/auth/Strategy/jwt.strategy.ts create mode 100644 src/config/env.ts create mode 100644 src/modules/auth/Decorators/roles.decorator.ts rename src/{ => modules}/auth/Guards/discord.guard.ts (100%) rename src/{ => modules}/auth/Guards/jwt.guard.ts (100%) create mode 100644 src/modules/auth/Guards/role.guard.ts create mode 100644 src/modules/auth/Strategy/discord.strategy.ts create mode 100644 src/modules/auth/Strategy/jwt.strategy.ts rename src/{ => modules}/auth/auth.controller.ts (61%) rename src/{ => modules}/auth/auth.module.ts (82%) rename src/{ => modules}/auth/auth.service.ts (100%) rename src/{ => modules}/prisma/prisma.service.ts (100%) create mode 100644 src/modules/user/dto/bulk-delete-user.dto.ts create mode 100644 src/modules/user/dto/create-user.dto.ts rename src/{user/dto/update-user.input.ts => modules/user/dto/update-user.dto.ts} (54%) create mode 100644 src/modules/user/user.controller.ts create mode 100644 src/modules/user/user.entity.ts rename src/{ => modules}/user/user.module.ts (64%) create mode 100644 src/modules/user/user.service.ts delete mode 100644 src/user/dto/create-user.input.ts delete mode 100644 src/user/dto/setpassword-user.input.ts delete mode 100644 src/user/user.controller.ts delete mode 100644 src/user/user.entity.ts delete mode 100644 src/user/user.service.ts create mode 100644 src/validations/env.validation.ts diff --git a/.env.example b/.env.example index 11ab557..03df36a 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,11 @@ -JWT_SECRET= \ No newline at end of file +DATABASE_URL="mysql://USER:PASS@IP:PORT/DB" + +JWT_SECRET= +JWT_EXPIRES_IN=1h + +REFRESH_JWT_SECRET= +REFRESH_JWT_EXPIRES_IN=7d + +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= +DISCORD_CALLBACK_URL=http://localhost:3000/auth/discord/callback \ No newline at end of file diff --git a/package.json b/package.json index 7b6cfa5..43800a5 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "axios": "^1.7.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "joi": "^17.13.3", "passport": "^0.7.0", "passport-discord": "^0.1.4", "passport-jwt": "^4.0.1", @@ -38,6 +39,7 @@ "@types/express": "^5.0.0", "@types/node": "^20.3.1", "@types/passport-discord": "^0.1.14", + "@types/passport-jwt": "^4.0.1", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.0.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6b11836..0065263 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -60,5 +60,5 @@ model RoomDocument { enum Role { STUDENT - TEACHER + ADMIN } diff --git a/src/app.module.ts b/src/app.module.ts index a244aaa..9a6019a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,24 +1,31 @@ +import env from "@Config/env"; import { Module } from "@nestjs/common"; -import { ConfigModule } from "@nestjs/config"; +import { ConfigModule, ConfigService } from "@nestjs/config"; import { JwtModule } from "@nestjs/jwt"; +import { envValidation } from "@Validations/env.validation"; -import { AppService } from "./app.service"; -import { UserModule } from "./user/user.module"; - -import { AuthModule } from './auth/auth.module'; +import { AuthModule } from "@Modules/auth/auth.module"; +import { UserModule } from "@Modules/user/user.module"; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, + load: [env], + validationSchema: envValidation, }), - JwtModule.register({ + JwtModule.registerAsync({ global: true, - secret: process.env.JWT_SECRET, + inject: [ConfigService], + useFactory: async (configService: ConfigService) => ({ + secret: configService.get("JWT.secret"), + signOptions: { + expiresIn: configService.get("JWT.expiresIn"), + }, + }), }), UserModule, AuthModule, ], - providers: [AppService], }) export class AppModule {} diff --git a/src/app.service.ts b/src/app.service.ts deleted file mode 100644 index 0d3afc7..0000000 --- a/src/app.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Injectable } from "@nestjs/common"; - -@Injectable() -export class AppService { - getHello() { - return { - message: "Hello World!", - }; - } -} diff --git a/src/auth/Strategy/discord.strategy.ts b/src/auth/Strategy/discord.strategy.ts deleted file mode 100644 index 39f104f..0000000 --- a/src/auth/Strategy/discord.strategy.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { JwtService } from "@nestjs/jwt"; -import { PassportStrategy } from "@nestjs/passport"; -import { Profile, Strategy } from "passport-discord"; - -@Injectable() -export class DiscordStrategy extends PassportStrategy(Strategy, "discord") { - constructor(private jwtService: JwtService) { - super({ - clientID: process.env.DISCORD_CLIENT_ID, - clientSecret: process.env.DISCORD_CLIENT_SECRET, - callbackURL: process.env.DISCORD_CALLBACK_URL, - scope: ["identify", "email"] - }); - } - - async validate( - _accessToken: string, - _refreshToken: string, - profile: Profile, - done: Function, - ) { - const jwtPayload = { - id: profile.id, - username: profile.username, - email: profile.email, - }; - - const jwt = this.jwtService.sign(jwtPayload); - - done(null, { jwt }); - } -} diff --git a/src/auth/Strategy/jwt.strategy.ts b/src/auth/Strategy/jwt.strategy.ts deleted file mode 100644 index 38b3137..0000000 --- a/src/auth/Strategy/jwt.strategy.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { PassportStrategy } from "@nestjs/passport"; -import { Profile } from "passport-discord"; -import { Strategy, ExtractJwt } from "passport-jwt"; - -@Injectable() -export class JWTStrategy extends PassportStrategy(Strategy) { - constructor() { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExipration: false, - secretOrKey: process.env.JWT_SECRET, - }); - } - - async validate(profile: Profile): Promise { - return profile; - } -} diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..f1e031f --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,17 @@ +export default () => ({ + JWT: { + secret: process.env.JWT_SECRET, + expiresIn: process.env.JWT_EXPIRES_IN, + refresh: { + secret: process.env.REFRESH_JWT_SECRET, + expiresIn: process.env.REFRESH_JWT_EXPIRES_IN, + }, + }, + oauth2: { + discord: { + clientId: process.env.DISCORD_CLIENT_ID, + clientSecret: process.env.DISCORD_CLIENT_SECRET, + callbackUrl: process.env.DISCORD_CALLBACK_URL, + }, + }, +}); diff --git a/src/main.ts b/src/main.ts index bb1c9e3..7009b44 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ -import { NestFactory } from "@nestjs/core"; +import { NestFactory, Reflector } from "@nestjs/core"; import { AppModule } from "./app.module"; -import { ValidationPipe } from "@nestjs/common"; +import { ClassSerializerInterceptor, ValidationPipe } from "@nestjs/common"; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -11,6 +11,10 @@ async function bootstrap() { app.useGlobalPipes(new ValidationPipe()); + app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector), { + excludeExtraneousValues: true, + })); + await app.listen(process.env.PORT ?? 3000, "0.0.0.0"); } bootstrap(); diff --git a/src/modules/auth/Decorators/roles.decorator.ts b/src/modules/auth/Decorators/roles.decorator.ts new file mode 100644 index 0000000..cc689b2 --- /dev/null +++ b/src/modules/auth/Decorators/roles.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from "@nestjs/common"; +import { $Enums } from "@prisma/client"; + +export const Role = (role: $Enums.Role) => SetMetadata("role", role); diff --git a/src/auth/Guards/discord.guard.ts b/src/modules/auth/Guards/discord.guard.ts similarity index 100% rename from src/auth/Guards/discord.guard.ts rename to src/modules/auth/Guards/discord.guard.ts diff --git a/src/auth/Guards/jwt.guard.ts b/src/modules/auth/Guards/jwt.guard.ts similarity index 100% rename from src/auth/Guards/jwt.guard.ts rename to src/modules/auth/Guards/jwt.guard.ts diff --git a/src/modules/auth/Guards/role.guard.ts b/src/modules/auth/Guards/role.guard.ts new file mode 100644 index 0000000..68c3c4b --- /dev/null +++ b/src/modules/auth/Guards/role.guard.ts @@ -0,0 +1,39 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, + UnauthorizedException, +} from "@nestjs/common"; +import { Reflector } from "@nestjs/core"; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private readonly reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const role = this.reflector.get( + "role", + context.getHandler(), + ); + if (!role) { + return true; + } + + const request = context.switchToHttp().getRequest(); + const user = request.user; + + if (!user) { + throw new ForbiddenException("User not authenticated"); + } + + const hasRole = role === user.role; + if (!hasRole) { + throw new UnauthorizedException( + `You need to have the role ${role} to access this resource`, + ); + } + + return true; + } +} diff --git a/src/modules/auth/Strategy/discord.strategy.ts b/src/modules/auth/Strategy/discord.strategy.ts new file mode 100644 index 0000000..7d7e5cf --- /dev/null +++ b/src/modules/auth/Strategy/discord.strategy.ts @@ -0,0 +1,37 @@ +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { JwtService } from "@nestjs/jwt"; +import { PassportStrategy } from "@nestjs/passport"; +import { Profile, Strategy } from "passport-discord"; + +@Injectable() +export class DiscordStrategy extends PassportStrategy(Strategy, "discord") { + constructor( + private readonly jwtService: JwtService, + configService: ConfigService, + ) { + super({ + clientID: configService.get("oauth2.discord.clientId"), + clientSecret: configService.get( + "oauth2.discord.clientSecret", + ), + callbackURL: configService.get( + "oauth2.discord.callbackUrl", + ), + scope: ["identify", "email"], + }); + } + + async validate( + _accessToken: string, + _refreshToken: string, + profile: Profile, + done: Function, + ) { + const jwt = this.jwtService.sign({ + id: profile.id, + }); + + done(null, { jwt }); + } +} diff --git a/src/modules/auth/Strategy/jwt.strategy.ts b/src/modules/auth/Strategy/jwt.strategy.ts new file mode 100644 index 0000000..91a02c3 --- /dev/null +++ b/src/modules/auth/Strategy/jwt.strategy.ts @@ -0,0 +1,30 @@ +import { Injectable, UnauthorizedException } from "@nestjs/common"; +import { PassportStrategy } from "@nestjs/passport"; +import { User } from "@prisma/client"; +import { Strategy, ExtractJwt } from "passport-jwt"; +import { UserService } from "@Modules/user/user.service"; +import { ConfigService } from "@nestjs/config"; + +@Injectable() +export class JWTStrategy extends PassportStrategy(Strategy) { + constructor( + private readonly userService: UserService, + private readonly configService: ConfigService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExipration: false, + secretOrKey: configService.get("JWT.secret"), + }); + } + + async validate(payload: any): Promise { + const user = await this.userService.findById(payload.id); + + if (!user) { + throw new UnauthorizedException("User not found"); + } + + return user; + } +} diff --git a/src/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts similarity index 61% rename from src/auth/auth.controller.ts rename to src/modules/auth/auth.controller.ts index c61165a..762a6a5 100644 --- a/src/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -1,16 +1,18 @@ -import { Controller, Get, Query, Req, Res, UseGuards } from "@nestjs/common"; +import { Controller, Get, Req, Res, UseGuards } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; import { URLSearchParams } from "node:url"; -import { JwtAuthGuard } from "./Guards/jwt.guard"; import { DiscordAuthGuard } from "./Guards/discord.guard"; @Controller("auth") export class AuthController { + constructor(private readonly configService: ConfigService) {} + @Get("providers") - Providers() { + getProviders() { const discordOauth2Params = new URLSearchParams({ - client_id: process.env.DISCORD_CLIENT_ID, + client_id: this.configService.get("oauth2.discord.clientId"), response_type: "code", - redirect_uri: process.env.DISCORD_CALLBACK_URL, + redirect_uri: this.configService.get("oauth2.discord.callbackUrl"), scope: "identify email", }); @@ -28,10 +30,4 @@ export class AuthController { res.send(user); } - - @Get("profile") - @UseGuards(JwtAuthGuard) - Profile(@Req() req) { - return req.user; - } } diff --git a/src/auth/auth.module.ts b/src/modules/auth/auth.module.ts similarity index 82% rename from src/auth/auth.module.ts rename to src/modules/auth/auth.module.ts index 052cff5..66c44ee 100644 --- a/src/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -2,10 +2,12 @@ import { Module } from "@nestjs/common"; import { AuthController } from "./auth.controller"; import { AuthService } from "./auth.service"; +import { UserModule } from "@Modules/user/user.module"; import { DiscordStrategy } from "./Strategy/discord.strategy"; import { JWTStrategy } from "./Strategy/jwt.strategy"; @Module({ + imports: [UserModule], controllers: [AuthController], providers: [AuthService, DiscordStrategy, JWTStrategy], }) diff --git a/src/auth/auth.service.ts b/src/modules/auth/auth.service.ts similarity index 100% rename from src/auth/auth.service.ts rename to src/modules/auth/auth.service.ts diff --git a/src/prisma/prisma.service.ts b/src/modules/prisma/prisma.service.ts similarity index 100% rename from src/prisma/prisma.service.ts rename to src/modules/prisma/prisma.service.ts diff --git a/src/modules/user/dto/bulk-delete-user.dto.ts b/src/modules/user/dto/bulk-delete-user.dto.ts new file mode 100644 index 0000000..c2ca738 --- /dev/null +++ b/src/modules/user/dto/bulk-delete-user.dto.ts @@ -0,0 +1,9 @@ +import { ArrayMaxSize, ArrayMinSize, IsArray, IsString } from "class-validator"; + +export class BulkDeleteUserDTO { + @IsArray() + @IsString({ each: true }) + @ArrayMinSize(1) + @ArrayMaxSize(10) + ids: string[]; +} diff --git a/src/modules/user/dto/create-user.dto.ts b/src/modules/user/dto/create-user.dto.ts new file mode 100644 index 0000000..201604c --- /dev/null +++ b/src/modules/user/dto/create-user.dto.ts @@ -0,0 +1,6 @@ +import { IsString } from "class-validator"; + +export class CreateUserDTO { + @IsString() + username: string; +} diff --git a/src/user/dto/update-user.input.ts b/src/modules/user/dto/update-user.dto.ts similarity index 54% rename from src/user/dto/update-user.input.ts rename to src/modules/user/dto/update-user.dto.ts index 2b2df1c..91f6a9b 100644 --- a/src/user/dto/update-user.input.ts +++ b/src/modules/user/dto/update-user.dto.ts @@ -1,9 +1,9 @@ import { PartialType } from "@nestjs/mapped-types"; import { IsString } from "class-validator"; -import { CreateUserInput } from "./create-user.input"; +import { CreateUserDTO } from "./create-user.dto"; -export class UpdateUserInput extends PartialType(CreateUserInput) { +export class UpdateUserDTO extends PartialType(CreateUserDTO) { @IsString() id: string; diff --git a/src/modules/user/user.controller.ts b/src/modules/user/user.controller.ts new file mode 100644 index 0000000..c282dcb --- /dev/null +++ b/src/modules/user/user.controller.ts @@ -0,0 +1,52 @@ +import { + Body, + Controller, + Delete, + Get, + Post, + Query, + UseGuards, +} from "@nestjs/common"; + +import { Role } from "@Modules/auth/Decorators/roles.decorator"; +import { JwtAuthGuard } from "@Modules/auth/Guards/jwt.guard"; +import { RolesGuard } from "@Modules/auth/Guards/role.guard"; + +import { BulkDeleteUserDTO } from "./dto/bulk-delete-user.dto"; +import { CreateUserDTO } from "./dto/create-user.dto"; + +import { UserEntity } from "./user.entity"; +import { UserService } from "./user.service"; + +@Controller("user") +@UseGuards(RolesGuard) +@UseGuards(JwtAuthGuard) +export class UserController { + constructor(private readonly userService: UserService) {} + + @Get() + @Role("ADMIN") + async findAll(): Promise { + const users = await this.userService.findAll(); + + return users.map((user) => new UserEntity(user)); + } + + @Post() + @Role("ADMIN") + create(@Body() createUserInput: CreateUserDTO) { + return this.userService.create(createUserInput); + } + + @Delete() + @Role("ADMIN") + delete(@Query("id") id: string) { + return this.userService.delete(id); + } + + @Delete("/bulk") + @Role("ADMIN") + bulkDelete(@Body() { ids }: BulkDeleteUserDTO) { + return this.userService.bulkDelete(ids); + } +} diff --git a/src/modules/user/user.entity.ts b/src/modules/user/user.entity.ts new file mode 100644 index 0000000..be0cef9 --- /dev/null +++ b/src/modules/user/user.entity.ts @@ -0,0 +1,17 @@ +import { $Enums } from "@prisma/client"; +import { Expose } from "class-transformer"; + +export class UserEntity { + @Expose() + id: string; + + @Expose() + username: string; + + @Expose() + role: $Enums.Role + + constructor(partial: Partial) { + Object.assign(this, partial); + } +} diff --git a/src/user/user.module.ts b/src/modules/user/user.module.ts similarity index 64% rename from src/user/user.module.ts rename to src/modules/user/user.module.ts index ed60dad..7e255d9 100644 --- a/src/user/user.module.ts +++ b/src/modules/user/user.module.ts @@ -2,11 +2,12 @@ import { Module } from "@nestjs/common"; import { UserService } from "./user.service"; -import { PrismaService } from "src/prisma/prisma.service"; +import { PrismaService } from "@Modules/prisma/prisma.service"; import { UserController } from './user.controller'; @Module({ providers: [UserService, PrismaService], - controllers: [UserController] + controllers: [UserController], + exports: [UserService], }) export class UserModule {} diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts new file mode 100644 index 0000000..9c0f24a --- /dev/null +++ b/src/modules/user/user.service.ts @@ -0,0 +1,56 @@ +import { Injectable, NotFoundException } from "@nestjs/common"; +import { PrismaService } from "@Modules/prisma/prisma.service"; + +import { CreateUserDTO } from "./dto/create-user.dto"; +import { UpdateUserDTO } from "./dto/update-user.dto"; + +@Injectable() +export class UserService { + constructor(private readonly prisma: PrismaService) {} + + async findAll() { + return await this.prisma.user.findMany(); + } + + async findById(id: string) { + return await this.prisma.user.findUnique({ + where: { id }, + }); + } + + async create(createUserInput: CreateUserDTO) { + return await this.prisma.user.create({ + data: { + username: createUserInput.username, + }, + }); + } + + async update(updateUserInput: UpdateUserDTO) { + return await this.prisma.user.update({ + where: { id: updateUserInput.id }, + data: { + username: updateUserInput.username, + }, + }); + } + + async delete(id: string) { + const exist = await this.prisma.user.findUnique({ where: { id } }); + if (!exist) throw new NotFoundException("User not found"); + + return await this.prisma.user.delete({ + where: { id }, + }); + } + + async bulkDelete(ids: string[]) { + return await this.prisma.user.deleteMany({ + where: { + id: { + in: ids, + }, + }, + }) + } +} diff --git a/src/user/dto/create-user.input.ts b/src/user/dto/create-user.input.ts deleted file mode 100644 index 234c7f3..0000000 --- a/src/user/dto/create-user.input.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IsBoolean, IsString } from "class-validator"; - -export class CreateUserInput { - @IsString() - username: string; - - @IsBoolean() - isAdmin: boolean; -} diff --git a/src/user/dto/setpassword-user.input.ts b/src/user/dto/setpassword-user.input.ts deleted file mode 100644 index 7da0345..0000000 --- a/src/user/dto/setpassword-user.input.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IsString } from "class-validator"; - -export class SetUserPasswordInput { - @IsString() - id: string; - - @IsString() - password: string; -} diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts deleted file mode 100644 index 6f6bd38..0000000 --- a/src/user/user.controller.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - Body, - Controller, - Delete, - Get, - Param, - Post, - UseGuards, -} from "@nestjs/common"; -import { UserService } from "./user.service"; - -import { JwtAuthGuard } from "src/auth/Guards/jwt.guard"; -import { CreateUserInput } from "./dto/create-user.input"; - -@Controller("user") -@UseGuards(JwtAuthGuard) -export class UserController { - constructor(private readonly userService: UserService) {} - - @Post() - create(@Body() createUserInput: CreateUserInput) { - return this.userService.create(createUserInput); - } - - @Get() - findAll() { - return this.userService.findAll(); - } - - @Delete(":id") - removeUser(@Param("id") id: string) { - return this.userService.remove(id); - } -} diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts deleted file mode 100644 index f9ae7d4..0000000 --- a/src/user/user.entity.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Exclude } from "class-transformer"; - -export class UserEntity { - id: string; - - username: string; - - isAdmin: boolean; - - @Exclude() - password: string; -} diff --git a/src/user/user.service.ts b/src/user/user.service.ts deleted file mode 100644 index 1120c8a..0000000 --- a/src/user/user.service.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Injectable, NotFoundException } from "@nestjs/common"; -import { plainToClass } from "class-transformer"; -import { PrismaService } from "src/prisma/prisma.service"; - -import { CreateUserInput } from "./dto/create-user.input"; -import { UpdateUserInput } from "./dto/update-user.input"; -import { SetUserPasswordInput } from "./dto/setpassword-user.input"; -import { UserEntity } from "./user.entity"; - -@Injectable() -export class UserService { - constructor(private readonly prisma: PrismaService) {} - - async create(createUserInput: CreateUserInput) { - const user = await this.prisma.user.create({ - data: { - username: createUserInput.username, - isAdmin: createUserInput.isAdmin, - }, - }); - - return plainToClass(UserEntity, user); - } - - async update(updateUserInput: UpdateUserInput) { - const user = await this.prisma.user.update({ - where: { id: updateUserInput.id }, - data: { - username: updateUserInput.username, - isAdmin: updateUserInput.isAdmin, - }, - }); - - return plainToClass(UserEntity, user); - } - - async setPassword(setUserPasswordInput: SetUserPasswordInput) { - const exist = await this.prisma.user.findUnique({ - where: { id: setUserPasswordInput.id }, - }); - if (!exist) throw new NotFoundException("User not found"); - - const user = await this.prisma.user.update({ - where: { id: setUserPasswordInput.id }, - data: { - password: setUserPasswordInput.password, - }, - }); - - return plainToClass(UserEntity, user); - } - - async findAll() { - const users = await this.prisma.user.findMany(); - - return users.map((user) => plainToClass(UserEntity, user)); - } - - async findOne(id: string) { - const user = await this.prisma.user.findUnique({ - where: { id }, - }); - - return plainToClass(UserEntity, user); - } - - async remove(id: string) { - const exist = await this.prisma.user.findUnique({ where: { id } }); - if (!exist) throw new NotFoundException("User not found"); - - const user = await this.prisma.user.delete({ - where: { id }, - }); - - return plainToClass(UserEntity, user); - } -} diff --git a/src/validations/env.validation.ts b/src/validations/env.validation.ts new file mode 100644 index 0000000..9d6c8fd --- /dev/null +++ b/src/validations/env.validation.ts @@ -0,0 +1,17 @@ +import * as Joi from 'joi'; + +export const envValidation = Joi.object({ + DATABASE_URL: Joi.string().required(), + + JWT_SECRET: Joi.string().required(), + JWT_EXPIRES_IN: Joi.string().required(), + + REFRESH_JWT_SECRET: Joi.string().required(), + REFRESH_JWT_EXPIRES_IN: Joi.string().required(), + + DISCORD_CLIENT_ID: Joi.string().required(), + DISCORD_CLIENT_SECRET: Joi.string().required(), + DISCORD_CALLBACK_URL: Joi.string().required(), + + PORT: Joi.number().optional(), +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8f5aedf..c6e5325 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,14 @@ "target": "ES2021", "sourceMap": true, "outDir": "./dist", - "baseUrl": "./", + "baseUrl": "./src", + "paths": { + "@/*": ["*"], + "@Modules/*": ["modules/*"], + "@Interfaces/*": ["interfaces/*"], + "@Config/*": ["config/*"], + "@Validations/*": ["validations/*"], + }, "incremental": true, "skipLibCheck": true, "strictNullChecks": false, diff --git a/yarn.lock b/yarn.lock index 593103b..ab71305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,6 +121,18 @@ dependencies: levn "^0.4.1" +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" @@ -397,6 +409,23 @@ dependencies: "@prisma/debug" "5.22.0" +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@tsconfig/node10@^1.0.7": version "1.0.11" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" @@ -483,6 +512,13 @@ 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.7" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2" + integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg== + dependencies: + "@types/node" "*" + "@types/jsonwebtoken@9.0.5": version "9.0.5" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588" @@ -525,6 +561,14 @@ "@types/passport" "*" "@types/passport-oauth2" "*" +"@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-oauth2@*": version "1.4.17" resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4" @@ -534,6 +578,14 @@ "@types/oauth" "*" "@types/passport" "*" +"@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@*": version "1.0.17" resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.17.tgz#718a8d1f7000ebcf6bbc0853da1bc8c4bc7ea5e6" @@ -2057,6 +2109,17 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" +joi@^17.13.3: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"