mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-10 07:07:21 +00:00
authentication module done
This commit is contained in:
8
apps/auth/src/config/app.config.ts
Normal file
8
apps/auth/src/config/app.config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default () => ({
|
||||||
|
DB_HOST: process.env.DB_HOST,
|
||||||
|
DB_PORT: process.env.DB_PORT,
|
||||||
|
DB_USER: process.env.DB_USER,
|
||||||
|
DB_PASSWORD: process.env.DB_PASSWORD,
|
||||||
|
DB_NAME: process.env.DB_NAME,
|
||||||
|
DB_SYNC: process.env.DB_SYNC,
|
||||||
|
});
|
@ -1,3 +1,4 @@
|
|||||||
import AuthConfig from './auth.config';
|
import AuthConfig from './auth.config';
|
||||||
|
import AppConfig from './app.config'
|
||||||
|
|
||||||
export default [AuthConfig];
|
export default [AuthConfig,AppConfig];
|
||||||
|
11
apps/auth/src/config/jwt.config.ts
Normal file
11
apps/auth/src/config/jwt.config.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs(
|
||||||
|
'jwt',
|
||||||
|
(): Record<string, any> => ({
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
expire_time: process.env.JWT_EXPIRE_TIME,
|
||||||
|
secret_refresh: process.env.JWT_SECRET_REFRESH,
|
||||||
|
expire_refresh: process.env.JWT_EXPIRE_TIME_REFRESH,
|
||||||
|
})
|
||||||
|
);
|
@ -1,8 +1,32 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AuthModule } from './auth.module';
|
import { AuthModule } from './auth.module';
|
||||||
|
import rateLimit from 'express-rate-limit';
|
||||||
|
import helmet from 'helmet';
|
||||||
|
import { setupSwaggerAuthentication } from '@app/common/util/user-auth.swagger.utils';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AuthModule);
|
const app = await NestFactory.create(AuthModule);
|
||||||
|
|
||||||
|
app.enableCors();
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
rateLimit({
|
||||||
|
windowMs: 5 * 60 * 1000,
|
||||||
|
max: 500,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
helmet({
|
||||||
|
contentSecurityPolicy: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
setupSwaggerAuthentication(app);
|
||||||
|
|
||||||
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
|
|
||||||
await app.listen(6001);
|
await app.listen(6001);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
@ -2,11 +2,24 @@ import { Module } from '@nestjs/common';
|
|||||||
import { AuthenticationController } from './controllers/authentication.controller';
|
import { AuthenticationController } from './controllers/authentication.controller';
|
||||||
import { AuthenticationService } from './services/authentication.service';
|
import { AuthenticationService } from './services/authentication.service';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||||
|
import { CommonModule } from '@app/common';
|
||||||
|
import { UserAuthController } from './controllers';
|
||||||
|
import { UserAuthService } from './services';
|
||||||
|
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||||
|
import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository';
|
||||||
|
import { UserOtpRepository } from '@app/common/modules/user-otp/repositories/user-otp.repository';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule, UserRepositoryModule, CommonModule],
|
||||||
controllers: [AuthenticationController],
|
controllers: [AuthenticationController, UserAuthController],
|
||||||
providers: [AuthenticationService],
|
providers: [
|
||||||
exports: [AuthenticationService],
|
AuthenticationService,
|
||||||
|
UserAuthService,
|
||||||
|
UserRepository,
|
||||||
|
UserSessionRepository,
|
||||||
|
UserOtpRepository,
|
||||||
|
],
|
||||||
|
exports: [AuthenticationService, UserAuthService],
|
||||||
})
|
})
|
||||||
export class AuthenticationModule {}
|
export class AuthenticationModule {}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export interface ILoginResponse {
|
||||||
|
access_token: string;
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
import { Controller, Post } from '@nestjs/common';
|
import { Controller, Post } from '@nestjs/common';
|
||||||
import { AuthenticationService } from '../services/authentication.service';
|
import { AuthenticationService } from '../services/authentication.service';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
version: '1',
|
version: '1',
|
||||||
path: 'authentication',
|
path: 'authentication',
|
||||||
})
|
})
|
||||||
|
@ApiTags('Tuya Auth')
|
||||||
export class AuthenticationController {
|
export class AuthenticationController {
|
||||||
constructor(private readonly authenticationService: AuthenticationService) {}
|
constructor(private readonly authenticationService: AuthenticationService) {}
|
||||||
@Post('auth')
|
@Post('auth')
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './authentication.controller';
|
||||||
|
export * from './user-auth.controller';
|
@ -0,0 +1,95 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { UserAuthService } from '../services/user-auth.service';
|
||||||
|
import { UserSignUpDto } from '../dtos/user-auth.dto';
|
||||||
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ResponseMessage } from '@app/common/response/response.decorator';
|
||||||
|
import { UserLoginDto } from '../dtos/user-login.dto';
|
||||||
|
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||||
|
import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos';
|
||||||
|
|
||||||
|
@Controller({
|
||||||
|
version: '1',
|
||||||
|
path: 'authentication',
|
||||||
|
})
|
||||||
|
@ApiTags('Auth')
|
||||||
|
export class UserAuthController {
|
||||||
|
constructor(private readonly userAuthService: UserAuthService) {}
|
||||||
|
|
||||||
|
@ResponseMessage('User Registered Successfully')
|
||||||
|
@Post('user/signup')
|
||||||
|
async signUp(@Body() userSignUpDto: UserSignUpDto) {
|
||||||
|
const signupUser = await this.userAuthService.signUp(userSignUpDto);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
data: {
|
||||||
|
id: signupUser.uuid,
|
||||||
|
},
|
||||||
|
message: 'User Registered Successfully',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseMessage('user logged in successfully')
|
||||||
|
@Post('user/login')
|
||||||
|
async userLogin(@Body() data: UserLoginDto) {
|
||||||
|
const accessToken = await this.userAuthService.userLogin(data);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
data: accessToken,
|
||||||
|
message: 'User Loggedin Successfully',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Delete('user/delete/:id')
|
||||||
|
async userDelete(@Param('id') id: string) {
|
||||||
|
await this.userAuthService.deleteUser(id);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
message: 'User Deleted Successfully',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('user/send-otp')
|
||||||
|
async sendOtp(@Body() otpDto: UserOtpDto) {
|
||||||
|
const otpCode = await this.userAuthService.generateOTP(otpDto);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
data: {
|
||||||
|
otp: otpCode,
|
||||||
|
},
|
||||||
|
message: 'Otp Send Successfully',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('user/verify-otp')
|
||||||
|
async verifyOtp(@Body() verifyOtpDto: VerifyOtpDto) {
|
||||||
|
await this.userAuthService.verifyOTP(verifyOtpDto);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
data: {},
|
||||||
|
message: 'Otp Verified Successfully',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('user/forget-password')
|
||||||
|
async forgetPassword(@Body() forgetPasswordDto: ForgetPasswordDto) {
|
||||||
|
await this.userAuthService.forgetPassword(forgetPasswordDto);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
data: {},
|
||||||
|
message: 'Password changed successfully',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
4
apps/auth/src/modules/authentication/dtos/index.ts
Normal file
4
apps/auth/src/modules/authentication/dtos/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './user-auth.dto'
|
||||||
|
export * from './user-login.dto'
|
||||||
|
export * from './user-otp.dto'
|
||||||
|
export * from './user-password.dto'
|
36
apps/auth/src/modules/authentication/dtos/user-auth.dto.ts
Normal file
36
apps/auth/src/modules/authentication/dtos/user-auth.dto.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UserSignUpDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description:'email',
|
||||||
|
required:true
|
||||||
|
})
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description:'password',
|
||||||
|
required:true
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public password: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description:'first name',
|
||||||
|
required:true
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public firstName: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description:'last name',
|
||||||
|
required:true
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public lastName: string;
|
||||||
|
}
|
14
apps/auth/src/modules/authentication/dtos/user-login.dto.ts
Normal file
14
apps/auth/src/modules/authentication/dtos/user-login.dto.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UserLoginDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
password: string;
|
||||||
|
}
|
22
apps/auth/src/modules/authentication/dtos/user-otp.dto.ts
Normal file
22
apps/auth/src/modules/authentication/dtos/user-otp.dto.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { OtpType } from '@app/common/constants/otp-type.enum';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEmail, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UserOtpDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsEnum(OtpType)
|
||||||
|
@IsNotEmpty()
|
||||||
|
type: OtpType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VerifyOtpDto extends UserOtpDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
otpCode: string;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class ForgetPasswordDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
password: string;
|
||||||
|
}
|
2
apps/auth/src/modules/authentication/services/index.ts
Normal file
2
apps/auth/src/modules/authentication/services/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './authentication.service';
|
||||||
|
export * from './user-auth.service';
|
@ -0,0 +1,151 @@
|
|||||||
|
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Injectable,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { UserSignUpDto } from '../dtos/user-auth.dto';
|
||||||
|
import { HelperHashService } from '@app/common/helper/services';
|
||||||
|
import { UserLoginDto } from '../dtos/user-login.dto';
|
||||||
|
import { AuthService } from '@app/common/auth/services/auth.service';
|
||||||
|
import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository';
|
||||||
|
import { UserOtpRepository } from '@app/common/modules/user-otp/repositories/user-otp.repository';
|
||||||
|
import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos';
|
||||||
|
import { EmailService } from '@app/common/util/email.service';
|
||||||
|
import { OtpType } from '@app/common/constants/otp-type.enum';
|
||||||
|
import { UserEntity } from '@app/common/modules/user/entities/user.entity';
|
||||||
|
import { ILoginResponse } from '../constants/login.response.constant';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserAuthService {
|
||||||
|
constructor(
|
||||||
|
private readonly userRepository: UserRepository,
|
||||||
|
private readonly sessionRepository: UserSessionRepository,
|
||||||
|
private readonly otpRepository: UserOtpRepository,
|
||||||
|
private readonly helperHashService: HelperHashService,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly emailService: EmailService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async signUp(userSignUpDto: UserSignUpDto): Promise<UserEntity> {
|
||||||
|
const findUser = await this.findUser(userSignUpDto.email);
|
||||||
|
if (findUser) {
|
||||||
|
throw new BadRequestException('User already registered with given email');
|
||||||
|
}
|
||||||
|
const salt = this.helperHashService.randomSalt(10);
|
||||||
|
const password = this.helperHashService.bcrypt(
|
||||||
|
userSignUpDto.password,
|
||||||
|
salt,
|
||||||
|
);
|
||||||
|
return await this.userRepository.save({ ...userSignUpDto, password });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findUser(email: string) {
|
||||||
|
return await this.userRepository.findOne({
|
||||||
|
where: {
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async forgetPassword(forgetPasswordDto: ForgetPasswordDto) {
|
||||||
|
const findUser = await this.findUser(forgetPasswordDto.email);
|
||||||
|
if (!findUser) {
|
||||||
|
throw new BadRequestException('User not found');
|
||||||
|
}
|
||||||
|
const salt = this.helperHashService.randomSalt(10);
|
||||||
|
const password = this.helperHashService.bcrypt(
|
||||||
|
forgetPasswordDto.password,
|
||||||
|
salt,
|
||||||
|
);
|
||||||
|
return await this.userRepository.update(
|
||||||
|
{ uuid: findUser.uuid },
|
||||||
|
{ password },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async userLogin(data: UserLoginDto): Promise<ILoginResponse> {
|
||||||
|
const user = await this.authService.validateUser(data.email, data.password);
|
||||||
|
if (!user) {
|
||||||
|
throw new UnauthorizedException('Invalid login credentials.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await Promise.all([
|
||||||
|
await this.sessionRepository.update(
|
||||||
|
{ userId: user.id },
|
||||||
|
{
|
||||||
|
isLoggedOut: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
await this.authService.createSession({
|
||||||
|
userId: user.uuid,
|
||||||
|
loginTime: new Date(),
|
||||||
|
isLoggedOut: false,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return await this.authService.login({
|
||||||
|
email: user.email,
|
||||||
|
userId: user.id,
|
||||||
|
uuid: user.uuid,
|
||||||
|
sessionId: session[1].uuid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(uuid: string) {
|
||||||
|
const user = await this.findOneById(uuid);
|
||||||
|
if (!user) {
|
||||||
|
throw new BadRequestException('User does not found');
|
||||||
|
}
|
||||||
|
return await this.userRepository.delete({ uuid });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneById(id: string): Promise<UserEntity> {
|
||||||
|
return await this.userRepository.findOne({ where: { uuid: id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateOTP(data: UserOtpDto): Promise<string> {
|
||||||
|
await this.otpRepository.delete({ email: data.email, type: data.type });
|
||||||
|
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
|
||||||
|
const expiryTime = new Date();
|
||||||
|
expiryTime.setMinutes(expiryTime.getMinutes() + 1);
|
||||||
|
await this.otpRepository.save({
|
||||||
|
email: data.email,
|
||||||
|
otpCode,
|
||||||
|
expiryTime,
|
||||||
|
type: data.type,
|
||||||
|
});
|
||||||
|
const subject = 'OTP send successfully';
|
||||||
|
const message = `Your OTP code is ${otpCode}`;
|
||||||
|
this.emailService.sendOTPEmail(data.email, subject, message);
|
||||||
|
return otpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyOTP(data: VerifyOtpDto): Promise<boolean> {
|
||||||
|
const otp = await this.otpRepository.findOne({
|
||||||
|
where: { email: data.email, type: data.type },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!otp) {
|
||||||
|
throw new BadRequestException('this email is not registered');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otp.otpCode !== data.otpCode) {
|
||||||
|
throw new BadRequestException('You entered wrong otp');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otp.expiryTime < new Date()) {
|
||||||
|
await this.otpRepository.delete(otp.id);
|
||||||
|
throw new BadRequestException('OTP expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type == OtpType.VERIFICATION) {
|
||||||
|
await this.userRepository.update(
|
||||||
|
{ email: data.email },
|
||||||
|
{ isUserVerified: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { AppService } from './backend.service';
|
|||||||
import { UserModule } from './modules/user/user.module';
|
import { UserModule } from './modules/user/user.module';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
import { CommonModule } from '@app/common';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -11,6 +12,7 @@ import config from './config';
|
|||||||
load: config,
|
load: config,
|
||||||
}),
|
}),
|
||||||
UserModule,
|
UserModule,
|
||||||
|
CommonModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
8
apps/backend/src/config/app.config.ts
Normal file
8
apps/backend/src/config/app.config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default () => ({
|
||||||
|
DB_HOST: process.env.DB_HOST,
|
||||||
|
DB_PORT: process.env.DB_PORT,
|
||||||
|
DB_USER: process.env.DB_USER,
|
||||||
|
DB_PASSWORD: process.env.DB_PASSWORD,
|
||||||
|
DB_NAME: process.env.DB_NAME,
|
||||||
|
DB_SYNC: process.env.DB_SYNC,
|
||||||
|
});
|
@ -1,3 +1,4 @@
|
|||||||
import AuthConfig from './auth.config';
|
import AuthConfig from './auth.config';
|
||||||
|
import AppConfig from './app.config'
|
||||||
export default [AuthConfig];
|
import JwtConfig from './jwt.config'
|
||||||
|
export default [AuthConfig,AppConfig,JwtConfig];
|
||||||
|
11
apps/backend/src/config/jwt.config.ts
Normal file
11
apps/backend/src/config/jwt.config.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs(
|
||||||
|
'jwt',
|
||||||
|
(): Record<string, any> => ({
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
expire_time: process.env.JWT_EXPIRE_TIME,
|
||||||
|
secret_refresh: process.env.JWT_SECRET_REFRESH,
|
||||||
|
expire_refresh: process.env.JWT_EXPIRE_TIME_REFRESH,
|
||||||
|
})
|
||||||
|
);
|
@ -1,8 +1,26 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { BackendModule } from './backend.module';
|
import { BackendModule } from './backend.module';
|
||||||
|
import rateLimit from 'express-rate-limit';
|
||||||
|
import helmet from 'helmet';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(BackendModule);
|
const app = await NestFactory.create(BackendModule);
|
||||||
|
app.enableCors();
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
rateLimit({
|
||||||
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
||||||
|
max: 500, // limit each IP to 500 requests per windowMs
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
helmet({
|
||||||
|
contentSecurityPolicy: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
await app.listen(6000);
|
await app.listen(6000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
1
apps/backend/src/modules/user/controllers/index.ts
Normal file
1
apps/backend/src/modules/user/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './user.controller'
|
@ -1,8 +1,9 @@
|
|||||||
import { Controller, Get, Query } from '@nestjs/common';
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import { UserListDto } from '../dtos/user.list.dto';
|
import { UserListDto } from '../dtos/user.list.dto';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
//@ApiTags('User Module')
|
@ApiTags('User Module')
|
||||||
@Controller({
|
@Controller({
|
||||||
version: '1',
|
version: '1',
|
||||||
path: 'user',
|
path: 'user',
|
||||||
|
1
apps/backend/src/modules/user/dtos/index.ts
Normal file
1
apps/backend/src/modules/user/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './user.list.dto'
|
1
apps/backend/src/modules/user/services/index.ts
Normal file
1
apps/backend/src/modules/user/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './user.service'
|
28
libs/common/src/auth/auth.module.ts
Normal file
28
libs/common/src/auth/auth.module.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { PassportModule } from '@nestjs/passport';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { HelperModule } from '../helper/helper.module';
|
||||||
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||||
|
import { UserSessionRepository } from '../modules/session/repositories/session.repository';
|
||||||
|
import { AuthService } from './services/auth.service';
|
||||||
|
import { UserRepository } from '../modules/user/repositories';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot(),
|
||||||
|
PassportModule,
|
||||||
|
JwtModule.registerAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
secret: configService.get('JWT_SECRET'),
|
||||||
|
signOptions: { expiresIn: configService.get('JWT_EXPIRE_TIME') },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
HelperModule,
|
||||||
|
],
|
||||||
|
providers: [JwtStrategy, UserSessionRepository,AuthService,UserRepository],
|
||||||
|
exports: [AuthService],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
7
libs/common/src/auth/interfaces/auth.interface.ts
Normal file
7
libs/common/src/auth/interfaces/auth.interface.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export class AuthInterface {
|
||||||
|
email: string;
|
||||||
|
userId: number;
|
||||||
|
uuid: string;
|
||||||
|
sessionId: string;
|
||||||
|
id: number;
|
||||||
|
}
|
55
libs/common/src/auth/services/auth.service.ts
Normal file
55
libs/common/src/auth/services/auth.service.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { HelperHashService } from '../../helper/services';
|
||||||
|
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||||
|
import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository';
|
||||||
|
import { UserSessionEntity } from '@app/common/modules/session/entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private readonly userRepository: UserRepository,
|
||||||
|
private readonly sessionRepository: UserSessionRepository,
|
||||||
|
private readonly helperHashService: HelperHashService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validateUser(email: string, pass: string): Promise<any> {
|
||||||
|
const user = await this.userRepository.findOne({
|
||||||
|
where: {
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!user.isUserVerified) {
|
||||||
|
throw new BadRequestException('User is not verified');
|
||||||
|
}
|
||||||
|
if (user) {
|
||||||
|
const passwordMatch = this.helperHashService.bcryptCompare(
|
||||||
|
pass,
|
||||||
|
user.password,
|
||||||
|
);
|
||||||
|
if (passwordMatch) {
|
||||||
|
const { ...result } = user;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession(data): Promise<UserSessionEntity> {
|
||||||
|
return await this.sessionRepository.save(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(user: any) {
|
||||||
|
const payload = {
|
||||||
|
email: user.email,
|
||||||
|
userId: user.userId,
|
||||||
|
uuid: user.uuid,
|
||||||
|
type: user.type,
|
||||||
|
sessionId: user.sessionId,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
access_token: this.jwtService.sign(payload),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
39
libs/common/src/auth/strategies/jwt.strategy.ts
Normal file
39
libs/common/src/auth/strategies/jwt.strategy.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository';
|
||||||
|
import { AuthInterface } from '../interfaces/auth.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
|
constructor(
|
||||||
|
private readonly sessionRepository: UserSessionRepository,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
ignoreExpiration: false,
|
||||||
|
secretOrKey: configService.get('JWT_SECRET'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(payload: AuthInterface) {
|
||||||
|
const validateUser = await this.sessionRepository.findOne({
|
||||||
|
where: {
|
||||||
|
uuid: payload.sessionId,
|
||||||
|
isLoggedOut: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (validateUser) {
|
||||||
|
return {
|
||||||
|
email: payload.email,
|
||||||
|
userId: payload.id,
|
||||||
|
uuid: payload.uuid,
|
||||||
|
sessionId: payload.sessionId,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException('Unauthorized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
libs/common/src/common.module.ts
Normal file
22
libs/common/src/common.module.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { CommonService } from './common.service';
|
||||||
|
import { DatabaseModule } from './database/database.module';
|
||||||
|
import { HelperModule } from './helper/helper.module';
|
||||||
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import config from './config';
|
||||||
|
import { EmailService } from './util/email.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [CommonService,EmailService],
|
||||||
|
exports: [CommonService, HelperModule, AuthModule,EmailService],
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
load: config,
|
||||||
|
}),
|
||||||
|
DatabaseModule,
|
||||||
|
HelperModule,
|
||||||
|
AuthModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CommonModule {}
|
18
libs/common/src/common.service.spec.ts
Normal file
18
libs/common/src/common.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { CommonService } from './common.service';
|
||||||
|
|
||||||
|
describe('CommonService', () => {
|
||||||
|
let service: CommonService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [CommonService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<CommonService>(CommonService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
4
libs/common/src/common.service.ts
Normal file
4
libs/common/src/common.service.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CommonService {}
|
9
libs/common/src/config/email.config.ts
Normal file
9
libs/common/src/config/email.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs(
|
||||||
|
'email-config',
|
||||||
|
(): Record<string, any> => ({
|
||||||
|
EMAIL_ID: process.env.EMAIL_USER,
|
||||||
|
PASSWORD: process.env.EMAIL_PASSWORD,
|
||||||
|
}),
|
||||||
|
);
|
2
libs/common/src/config/index.ts
Normal file
2
libs/common/src/config/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import emailConfig from './email.config';
|
||||||
|
export default [emailConfig];
|
4
libs/common/src/constants/otp-type.enum.ts
Normal file
4
libs/common/src/constants/otp-type.enum.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum OtpType {
|
||||||
|
VERIFICATION = 'VERIFICATION',
|
||||||
|
PASSWORD = 'PASSWORD',
|
||||||
|
}
|
38
libs/common/src/database/database.module.ts
Normal file
38
libs/common/src/database/database.module.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { SnakeNamingStrategy } from './strategies';
|
||||||
|
import { UserEntity } from '../modules/user/entities/user.entity';
|
||||||
|
import { UserSessionEntity } from '../modules/session/entities/session.entity';
|
||||||
|
import { UserOtpEntity } from '../modules/user-otp/entities';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
name: 'default',
|
||||||
|
type: 'postgres',
|
||||||
|
host: configService.get('DB_HOST'),
|
||||||
|
port: configService.get('DB_PORT'),
|
||||||
|
username: configService.get('DB_USER'),
|
||||||
|
password: configService.get('DB_PASSWORD'),
|
||||||
|
database: configService.get('DB_NAME'),
|
||||||
|
entities: [UserEntity, UserSessionEntity,UserOtpEntity],
|
||||||
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
|
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||||
|
logging: true,
|
||||||
|
extra: {
|
||||||
|
charset: 'utf8mb4',
|
||||||
|
max: 20, // set pool max size
|
||||||
|
idleTimeoutMillis: 5000, // close idle clients after 5 second
|
||||||
|
connectionTimeoutMillis: 11_000, // return an error after 11 second if connection could not be established
|
||||||
|
maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion)
|
||||||
|
},
|
||||||
|
continuationLocalStorage: true,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class DatabaseModule {}
|
1
libs/common/src/database/strategies/index.ts
Normal file
1
libs/common/src/database/strategies/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './snack-naming.strategy';
|
62
libs/common/src/database/strategies/snack-naming.strategy.ts
Normal file
62
libs/common/src/database/strategies/snack-naming.strategy.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { DefaultNamingStrategy, NamingStrategyInterface } from 'typeorm';
|
||||||
|
import { snakeCase } from 'typeorm/util/StringUtils';
|
||||||
|
|
||||||
|
export class SnakeNamingStrategy
|
||||||
|
extends DefaultNamingStrategy
|
||||||
|
implements NamingStrategyInterface
|
||||||
|
{
|
||||||
|
tableName(className: string, customName: string): string {
|
||||||
|
return customName ? customName : snakeCase(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
columnName(
|
||||||
|
propertyName: string,
|
||||||
|
customName: string,
|
||||||
|
embeddedPrefixes: string[],
|
||||||
|
): string {
|
||||||
|
return (
|
||||||
|
snakeCase(embeddedPrefixes.join('_')) +
|
||||||
|
(customName ? customName : snakeCase(propertyName))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
relationName(propertyName: string): string {
|
||||||
|
return snakeCase(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
joinColumnName(relationName: string, referencedColumnName: string): string {
|
||||||
|
return snakeCase(relationName + '_' + referencedColumnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
joinTableName(
|
||||||
|
firstTableName: string,
|
||||||
|
secondTableName: string,
|
||||||
|
firstPropertyName: any,
|
||||||
|
_secondPropertyName: string,
|
||||||
|
): string {
|
||||||
|
return snakeCase(
|
||||||
|
firstTableName +
|
||||||
|
'_' +
|
||||||
|
firstPropertyName.replaceAll(/\./gi, '_') +
|
||||||
|
'_' +
|
||||||
|
secondTableName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
joinTableColumnName(
|
||||||
|
tableName: string,
|
||||||
|
propertyName: string,
|
||||||
|
columnName?: string,
|
||||||
|
): string {
|
||||||
|
return snakeCase(
|
||||||
|
tableName + '_' + (columnName ? columnName : propertyName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
classTableInheritanceParentColumnName(
|
||||||
|
parentTableName: string,
|
||||||
|
parentTableIdPropertyName: string,
|
||||||
|
): string {
|
||||||
|
return snakeCase(`${parentTableName}_${parentTableIdPropertyName}`);
|
||||||
|
}
|
||||||
|
}
|
11
libs/common/src/guards/jwt.auth.guard.ts
Normal file
11
libs/common/src/guards/jwt.auth.guard.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||||
|
handleRequest(err, user, info) {
|
||||||
|
if (err || !user) {
|
||||||
|
throw err || new UnauthorizedException();
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
11
libs/common/src/helper/helper.module.ts
Normal file
11
libs/common/src/helper/helper.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { HelperHashService } from './services';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [HelperHashService],
|
||||||
|
exports: [HelperHashService],
|
||||||
|
controllers: [],
|
||||||
|
imports: [],
|
||||||
|
})
|
||||||
|
export class HelperModule {}
|
63
libs/common/src/helper/services/helper.hash.service.ts
Normal file
63
libs/common/src/helper/services/helper.hash.service.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { compareSync, genSaltSync, hashSync } from 'bcryptjs';
|
||||||
|
import { AES, enc, mode, pad, SHA256 } from 'crypto-js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HelperHashService {
|
||||||
|
randomSalt(length: number): string {
|
||||||
|
return genSaltSync(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bcrypt(passwordString: string, salt: string): string {
|
||||||
|
return hashSync(passwordString, salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bcryptCompare(passwordString: string, passwordHashed: string): boolean {
|
||||||
|
return compareSync(passwordString, passwordHashed);
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256(string: string): string {
|
||||||
|
return SHA256(string).toString(enc.Hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256Compare(hashOne: string, hashTwo: string): boolean {
|
||||||
|
return hashOne === hashTwo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encryption function
|
||||||
|
encryptPassword(password, secretKey) {
|
||||||
|
return AES.encrypt('trx8g6gi', secretKey).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decryption function
|
||||||
|
decryptPassword(encryptedPassword, secretKey) {
|
||||||
|
const bytes = AES.decrypt(encryptedPassword, secretKey);
|
||||||
|
return bytes.toString(enc.Utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
aes256Encrypt(
|
||||||
|
data: string | Record<string, any> | Record<string, any>[],
|
||||||
|
key: string,
|
||||||
|
iv: string,
|
||||||
|
): string {
|
||||||
|
const cIv = enc.Utf8.parse(iv);
|
||||||
|
const cipher = AES.encrypt(JSON.stringify(data), enc.Utf8.parse(key), {
|
||||||
|
mode: mode.CBC,
|
||||||
|
padding: pad.Pkcs7,
|
||||||
|
iv: cIv,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cipher.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
aes256Decrypt(encrypted: string, key: string, iv: string) {
|
||||||
|
const cIv = enc.Utf8.parse(iv);
|
||||||
|
const cipher = AES.decrypt(encrypted, enc.Utf8.parse(key), {
|
||||||
|
mode: mode.CBC,
|
||||||
|
padding: pad.Pkcs7,
|
||||||
|
iv: cIv,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cipher.toString(enc.Utf8);
|
||||||
|
}
|
||||||
|
}
|
1
libs/common/src/helper/services/index.ts
Normal file
1
libs/common/src/helper/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './helper.hash.service'
|
2
libs/common/src/index.ts
Normal file
2
libs/common/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './common.module';
|
||||||
|
export * from './common.service';
|
13
libs/common/src/modules/abstract/dtos/abstract.dto.ts
Normal file
13
libs/common/src/modules/abstract/dtos/abstract.dto.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { AbstractEntity } from '../entities/abstract.entity';
|
||||||
|
|
||||||
|
export class AbstractDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
readonly uuid: string;
|
||||||
|
|
||||||
|
constructor(abstract: AbstractEntity, options?: { excludeFields?: boolean }) {
|
||||||
|
if (!options?.excludeFields) {
|
||||||
|
this.uuid = abstract.uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
libs/common/src/modules/abstract/dtos/index.ts
Normal file
1
libs/common/src/modules/abstract/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './abstract.dto'
|
47
libs/common/src/modules/abstract/entities/abstract.entity.ts
Normal file
47
libs/common/src/modules/abstract/entities/abstract.entity.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Exclude } from 'class-transformer';
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Generated,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import { AbstractDto } from '../dtos';
|
||||||
|
import { Constructor } from '@app/common/util/types';
|
||||||
|
|
||||||
|
export abstract class AbstractEntity<
|
||||||
|
T extends AbstractDto = AbstractDto,
|
||||||
|
O = never
|
||||||
|
> {
|
||||||
|
@PrimaryGeneratedColumn('increment')
|
||||||
|
@Exclude()
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
@Generated('uuid')
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ type: 'timestamp' })
|
||||||
|
@Exclude()
|
||||||
|
public createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ type: 'timestamp' })
|
||||||
|
@Exclude()
|
||||||
|
public updatedAt: Date;
|
||||||
|
|
||||||
|
private dtoClass: Constructor<T, [AbstractEntity, O?]>;
|
||||||
|
|
||||||
|
toDto(options?: O): T {
|
||||||
|
const dtoClass = this.dtoClass;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (!dtoClass) {
|
||||||
|
throw new Error(
|
||||||
|
`You need to use @UseDto on class (${this.constructor.name}) be able to call toDto function`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new this.dtoClass(this, options);
|
||||||
|
}
|
||||||
|
}
|
26
libs/common/src/modules/session/dtos/session.dto.ts
Normal file
26
libs/common/src/modules/session/dtos/session.dto.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsDate,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsString,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export class SessionDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@IsDate()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public loginTime: Date;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public isLoggedOut: boolean;
|
||||||
|
|
||||||
|
}
|
1
libs/common/src/modules/session/entities/index.ts
Normal file
1
libs/common/src/modules/session/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './session.entity'
|
31
libs/common/src/modules/session/entities/session.entity.ts
Normal file
31
libs/common/src/modules/session/entities/session.entity.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Column, Entity } from 'typeorm';
|
||||||
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
import { SessionDto } from '../dtos/session.dto';
|
||||||
|
|
||||||
|
@Entity({ name: 'userSession' })
|
||||||
|
export class UserSessionEntity extends AbstractEntity<SessionDto> {
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public loginTime: Date;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public isLoggedOut: boolean;
|
||||||
|
|
||||||
|
constructor(partial: Partial<UserSessionEntity>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { UserSessionEntity } from '../entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserSessionRepository extends Repository<UserSessionEntity> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(UserSessionEntity, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
11
libs/common/src/modules/session/session.repository.module.ts
Normal file
11
libs/common/src/modules/session/session.repository.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { UserSessionEntity } from './entities';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
controllers: [],
|
||||||
|
imports: [TypeOrmModule.forFeature([UserSessionEntity])],
|
||||||
|
})
|
||||||
|
export class UserSessionRepositoryModule {}
|
1
libs/common/src/modules/user-otp/dtos/index.ts
Normal file
1
libs/common/src/modules/user-otp/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './user-otp.dto'
|
19
libs/common/src/modules/user-otp/dtos/user-otp.dto.ts
Normal file
19
libs/common/src/modules/user-otp/dtos/user-otp.dto.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UserOtpDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public otpCode: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public expiryTime: string;
|
||||||
|
}
|
1
libs/common/src/modules/user-otp/entities/index.ts
Normal file
1
libs/common/src/modules/user-otp/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './user-otp.entity'
|
32
libs/common/src/modules/user-otp/entities/user-otp.entity.ts
Normal file
32
libs/common/src/modules/user-otp/entities/user-otp.entity.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Column, Entity } from 'typeorm';
|
||||||
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
import { UserOtpDto } from '../dtos';
|
||||||
|
import { OtpType } from '@app/common/constants/otp-type.enum';
|
||||||
|
|
||||||
|
@Entity({ name: 'user-otp' })
|
||||||
|
export class UserOtpEntity extends AbstractEntity<UserOtpDto> {
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@Column({ nullable: false })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column({ nullable: false })
|
||||||
|
otpCode: string;
|
||||||
|
|
||||||
|
@Column({ nullable: false })
|
||||||
|
expiryTime: Date;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: Object.values(OtpType),
|
||||||
|
})
|
||||||
|
type: OtpType;
|
||||||
|
|
||||||
|
constructor(partial: Partial<UserOtpEntity>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { UserOtpEntity } from '../entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserOtpRepository extends Repository<UserOtpEntity> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(UserOtpEntity, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { UserOtpEntity } from './entities';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
controllers: [],
|
||||||
|
imports: [TypeOrmModule.forFeature([UserOtpEntity])],
|
||||||
|
})
|
||||||
|
export class UserOtpRepositoryModule {}
|
1
libs/common/src/modules/user/dtos/index.ts
Normal file
1
libs/common/src/modules/user/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './user.dto';
|
23
libs/common/src/modules/user/dtos/user.dto.ts
Normal file
23
libs/common/src/modules/user/dtos/user.dto.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UserDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public password: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public firstName: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public lastName: string;
|
||||||
|
}
|
0
libs/common/src/modules/user/entities/index.ts
Normal file
0
libs/common/src/modules/user/entities/index.ts
Normal file
41
libs/common/src/modules/user/entities/user.entity.ts
Normal file
41
libs/common/src/modules/user/entities/user.entity.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Column, Entity } from 'typeorm';
|
||||||
|
import { UserDto } from '../dtos';
|
||||||
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'user' })
|
||||||
|
export class UserEntity extends AbstractEntity<UserDto> {
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
unique: true,
|
||||||
|
})
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public password: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
public firstName: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public lastName: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: true,
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public isUserVerified: boolean;
|
||||||
|
|
||||||
|
constructor(partial: Partial<UserEntity>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
1
libs/common/src/modules/user/repositories/index.ts
Normal file
1
libs/common/src/modules/user/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './user.repository';
|
10
libs/common/src/modules/user/repositories/user.repository.ts
Normal file
10
libs/common/src/modules/user/repositories/user.repository.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { UserEntity } from '../entities/user.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserRepository extends Repository<UserEntity> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(UserEntity, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
11
libs/common/src/modules/user/user.repository.module.ts
Normal file
11
libs/common/src/modules/user/user.repository.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { UserEntity } from './entities/user.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
controllers: [],
|
||||||
|
imports: [TypeOrmModule.forFeature([UserEntity])],
|
||||||
|
})
|
||||||
|
export class UserRepositoryModule {}
|
4
libs/common/src/response/response.decorator.ts
Normal file
4
libs/common/src/response/response.decorator.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const ResponseMessage = (message: string) =>
|
||||||
|
SetMetadata('response_message', message);
|
5
libs/common/src/response/response.interceptor.ts
Normal file
5
libs/common/src/response/response.interceptor.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface Response<T> {
|
||||||
|
statusCode: number;
|
||||||
|
message: string;
|
||||||
|
data?: T;
|
||||||
|
}
|
38
libs/common/src/util/email.service.ts
Normal file
38
libs/common/src/util/email.service.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import * as nodemailer from 'nodemailer';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmailService {
|
||||||
|
private user: string;
|
||||||
|
private pass: string;
|
||||||
|
|
||||||
|
constructor(private readonly configService: ConfigService) {
|
||||||
|
this.user = this.configService.get<string>('email-config.EMAIL_ID');
|
||||||
|
this.pass = this.configService.get<string>('email-config.PASSWORD');
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendOTPEmail(
|
||||||
|
email: string,
|
||||||
|
subject: string,
|
||||||
|
message: string,
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
user: this.user,
|
||||||
|
pass: this.pass,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: this.user,
|
||||||
|
to: email,
|
||||||
|
subject,
|
||||||
|
text: message,
|
||||||
|
};
|
||||||
|
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
}
|
||||||
|
}
|
46
libs/common/src/util/types.ts
Normal file
46
libs/common/src/util/types.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export type Constructor<T, Arguments extends unknown[] = undefined[]> = new (
|
||||||
|
...arguments_: Arguments
|
||||||
|
) => T;
|
||||||
|
|
||||||
|
export type Plain<T> = T;
|
||||||
|
export type Optional<T> = T | undefined;
|
||||||
|
export type Nullable<T> = T | null;
|
||||||
|
|
||||||
|
export type PathImpl<T, Key extends keyof T> = Key extends string
|
||||||
|
? T[Key] extends Record<string, any>
|
||||||
|
?
|
||||||
|
| `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof any[]>> &
|
||||||
|
string}`
|
||||||
|
| `${Key}.${Exclude<keyof T[Key], keyof any[]> & string}`
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type PathImpl2<T> = PathImpl<T, keyof T> | keyof T;
|
||||||
|
|
||||||
|
export type Path<T> = keyof T extends string
|
||||||
|
? PathImpl2<T> extends string | keyof T
|
||||||
|
? PathImpl2<T>
|
||||||
|
: keyof T
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type PathValue<
|
||||||
|
T,
|
||||||
|
P extends Path<T>
|
||||||
|
> = P extends `${infer Key}.${infer Rest}`
|
||||||
|
? Key extends keyof T
|
||||||
|
? Rest extends Path<T[Key]>
|
||||||
|
? PathValue<T[Key], Rest>
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: P extends keyof T
|
||||||
|
? T[P]
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type KeyOfType<Entity, U> = {
|
||||||
|
[P in keyof Required<Entity>]: Required<Entity>[P] extends U
|
||||||
|
? P
|
||||||
|
: Required<Entity>[P] extends U[]
|
||||||
|
? P
|
||||||
|
: never;
|
||||||
|
}[keyof Entity];
|
||||||
|
|
21
libs/common/src/util/user-auth.swagger.utils.ts
Normal file
21
libs/common/src/util/user-auth.swagger.utils.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { INestApplication } from '@nestjs/common';
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export function setupSwaggerAuthentication(app: INestApplication): void {
|
||||||
|
const options = new DocumentBuilder()
|
||||||
|
.setTitle('Authentication-Service')
|
||||||
|
.addBearerAuth({
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
bearerFormat: 'JWT',
|
||||||
|
in: 'header',
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const document = SwaggerModule.createDocument(app, options);
|
||||||
|
SwaggerModule.setup('api/authentication/documentation', app, document, {
|
||||||
|
swaggerOptions: {
|
||||||
|
persistAuthorization: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
9
libs/common/tsconfig.lib.json
Normal file
9
libs/common/tsconfig.lib.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "../../dist/libs/common"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
|
||||||
|
}
|
@ -27,6 +27,15 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsConfigPath": "apps/auth/tsconfig.app.json"
|
"tsConfigPath": "apps/auth/tsconfig.app.json"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"type": "library",
|
||||||
|
"root": "libs/common",
|
||||||
|
"entryFile": "index",
|
||||||
|
"sourceRoot": "libs/common/src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsConfigPath": "libs/common/tsconfig.lib.json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
896
package-lock.json
generated
896
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@ -25,14 +25,27 @@
|
|||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.0",
|
"@nestjs/config": "^3.2.0",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/jwt": "^10.2.0",
|
||||||
|
"@nestjs/passport": "^10.0.3",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/swagger": "^7.3.0",
|
||||||
|
"@nestjs/typeorm": "^10.0.2",
|
||||||
"@tuya/tuya-connector-nodejs": "^2.1.2",
|
"@tuya/tuya-connector-nodejs": "^2.1.2",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"express-rate-limit": "^7.1.5",
|
||||||
|
"helmet": "^7.1.0",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"nodemailer": "^6.9.10",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"pg": "^8.11.3",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1",
|
||||||
|
"typeorm": "^0.3.20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
@ -74,7 +87,11 @@
|
|||||||
"coverageDirectory": "./coverage",
|
"coverageDirectory": "./coverage",
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"roots": [
|
"roots": [
|
||||||
"<rootDir>/apps/"
|
"<rootDir>/apps/",
|
||||||
]
|
"<rootDir>/libs/"
|
||||||
|
],
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^@app/common(|/.*)$": "<rootDir>/libs/common/src/$1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,13 @@
|
|||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"forceConsistentCasingInFileNames": false,
|
"forceConsistentCasingInFileNames": false,
|
||||||
"noFallthroughCasesInSwitch": false,
|
"noFallthroughCasesInSwitch": false,
|
||||||
"paths": {}
|
"paths": {
|
||||||
|
"@app/common": [
|
||||||
|
"libs/common/src"
|
||||||
|
],
|
||||||
|
"@app/common/*": [
|
||||||
|
"libs/common/src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user