feat: add ConfigService with Env validator
- add Role decorator
This commit is contained in:
parent
a95ed47302
commit
1988673f80
12
.env.example
12
.env.example
@ -1 +1,11 @@
|
|||||||
JWT_SECRET=
|
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
|
@ -25,6 +25,7 @@
|
|||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
|
"joi": "^17.13.3",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-discord": "^0.1.4",
|
"passport-discord": "^0.1.4",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
"@types/passport-discord": "^0.1.14",
|
"@types/passport-discord": "^0.1.14",
|
||||||
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
|
@ -60,5 +60,5 @@ model RoomDocument {
|
|||||||
|
|
||||||
enum Role {
|
enum Role {
|
||||||
STUDENT
|
STUDENT
|
||||||
TEACHER
|
ADMIN
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
|
import env from "@Config/env";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { ConfigModule } from "@nestjs/config";
|
import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
|
import { envValidation } from "@Validations/env.validation";
|
||||||
|
|
||||||
import { AppService } from "./app.service";
|
import { AuthModule } from "@Modules/auth/auth.module";
|
||||||
import { UserModule } from "./user/user.module";
|
import { UserModule } from "@Modules/user/user.module";
|
||||||
|
|
||||||
import { AuthModule } from './auth/auth.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
|
load: [env],
|
||||||
|
validationSchema: envValidation,
|
||||||
}),
|
}),
|
||||||
JwtModule.register({
|
JwtModule.registerAsync({
|
||||||
global: true,
|
global: true,
|
||||||
secret: process.env.JWT_SECRET,
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
secret: configService.get<string>("JWT.secret"),
|
||||||
|
signOptions: {
|
||||||
|
expiresIn: configService.get<string>("JWT.expiresIn"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
UserModule,
|
UserModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
],
|
],
|
||||||
providers: [AppService],
|
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AppService {
|
|
||||||
getHello() {
|
|
||||||
return {
|
|
||||||
message: "Hello World!",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 });
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<any> {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
}
|
|
17
src/config/env.ts
Normal file
17
src/config/env.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import { NestFactory } from "@nestjs/core";
|
import { NestFactory, Reflector } from "@nestjs/core";
|
||||||
import { AppModule } from "./app.module";
|
import { AppModule } from "./app.module";
|
||||||
import { ValidationPipe } from "@nestjs/common";
|
import { ClassSerializerInterceptor, ValidationPipe } from "@nestjs/common";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
@ -11,6 +11,10 @@ async function bootstrap() {
|
|||||||
|
|
||||||
app.useGlobalPipes(new ValidationPipe());
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
|
|
||||||
|
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector), {
|
||||||
|
excludeExtraneousValues: true,
|
||||||
|
}));
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3000, "0.0.0.0");
|
await app.listen(process.env.PORT ?? 3000, "0.0.0.0");
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
4
src/modules/auth/Decorators/roles.decorator.ts
Normal file
4
src/modules/auth/Decorators/roles.decorator.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { SetMetadata } from "@nestjs/common";
|
||||||
|
import { $Enums } from "@prisma/client";
|
||||||
|
|
||||||
|
export const Role = (role: $Enums.Role) => SetMetadata("role", role);
|
39
src/modules/auth/Guards/role.guard.ts
Normal file
39
src/modules/auth/Guards/role.guard.ts
Normal file
@ -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<string>(
|
||||||
|
"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;
|
||||||
|
}
|
||||||
|
}
|
37
src/modules/auth/Strategy/discord.strategy.ts
Normal file
37
src/modules/auth/Strategy/discord.strategy.ts
Normal file
@ -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<string>("oauth2.discord.clientId"),
|
||||||
|
clientSecret: configService.get<string>(
|
||||||
|
"oauth2.discord.clientSecret",
|
||||||
|
),
|
||||||
|
callbackURL: configService.get<string>(
|
||||||
|
"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 });
|
||||||
|
}
|
||||||
|
}
|
30
src/modules/auth/Strategy/jwt.strategy.ts
Normal file
30
src/modules/auth/Strategy/jwt.strategy.ts
Normal file
@ -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<string>("JWT.secret"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(payload: any): Promise<User> {
|
||||||
|
const user = await this.userService.findById(payload.id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new UnauthorizedException("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
@ -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 { URLSearchParams } from "node:url";
|
||||||
import { JwtAuthGuard } from "./Guards/jwt.guard";
|
|
||||||
import { DiscordAuthGuard } from "./Guards/discord.guard";
|
import { DiscordAuthGuard } from "./Guards/discord.guard";
|
||||||
|
|
||||||
@Controller("auth")
|
@Controller("auth")
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
|
constructor(private readonly configService: ConfigService) {}
|
||||||
|
|
||||||
@Get("providers")
|
@Get("providers")
|
||||||
Providers() {
|
getProviders() {
|
||||||
const discordOauth2Params = new URLSearchParams({
|
const discordOauth2Params = new URLSearchParams({
|
||||||
client_id: process.env.DISCORD_CLIENT_ID,
|
client_id: this.configService.get<string>("oauth2.discord.clientId"),
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
redirect_uri: process.env.DISCORD_CALLBACK_URL,
|
redirect_uri: this.configService.get<string>("oauth2.discord.callbackUrl"),
|
||||||
scope: "identify email",
|
scope: "identify email",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -28,10 +30,4 @@ export class AuthController {
|
|||||||
|
|
||||||
res.send(user);
|
res.send(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("profile")
|
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
Profile(@Req() req) {
|
|
||||||
return req.user;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,10 +2,12 @@ import { Module } from "@nestjs/common";
|
|||||||
import { AuthController } from "./auth.controller";
|
import { AuthController } from "./auth.controller";
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
|
|
||||||
|
import { UserModule } from "@Modules/user/user.module";
|
||||||
import { DiscordStrategy } from "./Strategy/discord.strategy";
|
import { DiscordStrategy } from "./Strategy/discord.strategy";
|
||||||
import { JWTStrategy } from "./Strategy/jwt.strategy";
|
import { JWTStrategy } from "./Strategy/jwt.strategy";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [UserModule],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService, DiscordStrategy, JWTStrategy],
|
providers: [AuthService, DiscordStrategy, JWTStrategy],
|
||||||
})
|
})
|
9
src/modules/user/dto/bulk-delete-user.dto.ts
Normal file
9
src/modules/user/dto/bulk-delete-user.dto.ts
Normal file
@ -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[];
|
||||||
|
}
|
6
src/modules/user/dto/create-user.dto.ts
Normal file
6
src/modules/user/dto/create-user.dto.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class CreateUserDTO {
|
||||||
|
@IsString()
|
||||||
|
username: string;
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { PartialType } from "@nestjs/mapped-types";
|
import { PartialType } from "@nestjs/mapped-types";
|
||||||
import { IsString } from "class-validator";
|
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()
|
@IsString()
|
||||||
id: string;
|
id: string;
|
||||||
|
|
52
src/modules/user/user.controller.ts
Normal file
52
src/modules/user/user.controller.ts
Normal file
@ -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<UserEntity[]> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
17
src/modules/user/user.entity.ts
Normal file
17
src/modules/user/user.entity.ts
Normal file
@ -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<UserEntity>) {
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,12 @@ import { Module } from "@nestjs/common";
|
|||||||
|
|
||||||
import { UserService } from "./user.service";
|
import { UserService } from "./user.service";
|
||||||
|
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "@Modules/prisma/prisma.service";
|
||||||
import { UserController } from './user.controller';
|
import { UserController } from './user.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [UserService, PrismaService],
|
providers: [UserService, PrismaService],
|
||||||
controllers: [UserController]
|
controllers: [UserController],
|
||||||
|
exports: [UserService],
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
56
src/modules/user/user.service.ts
Normal file
56
src/modules/user/user.service.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
import { IsBoolean, IsString } from "class-validator";
|
|
||||||
|
|
||||||
export class CreateUserInput {
|
|
||||||
@IsString()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
isAdmin: boolean;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import { IsString } from "class-validator";
|
|
||||||
|
|
||||||
export class SetUserPasswordInput {
|
|
||||||
@IsString()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
password: string;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Exclude } from "class-transformer";
|
|
||||||
|
|
||||||
export class UserEntity {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
isAdmin: boolean;
|
|
||||||
|
|
||||||
@Exclude()
|
|
||||||
password: string;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
17
src/validations/env.validation.ts
Normal file
17
src/validations/env.validation.ts
Normal file
@ -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(),
|
||||||
|
});
|
@ -9,7 +9,14 @@
|
|||||||
"target": "ES2021",
|
"target": "ES2021",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./src",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["*"],
|
||||||
|
"@Modules/*": ["modules/*"],
|
||||||
|
"@Interfaces/*": ["interfaces/*"],
|
||||||
|
"@Config/*": ["config/*"],
|
||||||
|
"@Validations/*": ["validations/*"],
|
||||||
|
},
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
|
63
yarn.lock
63
yarn.lock
@ -121,6 +121,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
levn "^0.4.1"
|
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":
|
"@humanfs/core@^0.19.1":
|
||||||
version "0.19.1"
|
version "0.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
|
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
|
||||||
@ -397,6 +409,23 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/debug" "5.22.0"
|
"@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":
|
"@tsconfig/node10@^1.0.7":
|
||||||
version "1.0.11"
|
version "1.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
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":
|
"@types/jsonwebtoken@9.0.5":
|
||||||
version "9.0.5"
|
version "9.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588"
|
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588"
|
||||||
@ -525,6 +561,14 @@
|
|||||||
"@types/passport" "*"
|
"@types/passport" "*"
|
||||||
"@types/passport-oauth2" "*"
|
"@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@*":
|
"@types/passport-oauth2@*":
|
||||||
version "1.4.17"
|
version "1.4.17"
|
||||||
resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4"
|
resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4"
|
||||||
@ -534,6 +578,14 @@
|
|||||||
"@types/oauth" "*"
|
"@types/oauth" "*"
|
||||||
"@types/passport" "*"
|
"@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@*":
|
"@types/passport@*":
|
||||||
version "1.0.17"
|
version "1.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.17.tgz#718a8d1f7000ebcf6bbc0853da1bc8c4bc7ea5e6"
|
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"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^8.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:
|
js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
Loading…
Reference in New Issue
Block a user