diff --git a/package.json b/package.json index 857b5d3..0107ba4 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "@nestjs/typeorm": "^10.0.2", "@nestjs/websockets": "^10.3.10", "@ssut/nestjs-sqs": "^2.2.0", + "@types/nodemailer": "^6.4.16", + "@types/passport-jwt": "^4.0.1", "@types/socket.io": "^3.0.2", "@willsoto/nestjs-prometheus": "^6.0.1", "aws-sdk": "^2.1623.0", diff --git a/src/app.module.ts b/src/app.module.ts index de71cb6..77fe478 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -29,7 +29,7 @@ const appModules = [ }), TypeOrmModule.forRootAsync({ useFactory: async (configService: ConfigService) => { - const datasource = JSON.parse(configService.get('datasource/db')); + const datasource = JSON.parse(configService.get('datasource/db')); return { type: 'postgres', @@ -48,7 +48,9 @@ const appModules = [ }), RedisModule.forRootAsync({ useFactory: async (configService: ConfigService) => { - const datasource = JSON.parse(configService.get('datasource/redis')); + const datasource = JSON.parse( + configService.get('datasource/redis'), + ); return { config: datasource, readyLog: true, diff --git a/src/auth/application/auth.service.ts b/src/auth/application/auth.service.ts index 4d3e862..84998e8 100644 --- a/src/auth/application/auth.service.ts +++ b/src/auth/application/auth.service.ts @@ -1,17 +1,18 @@ -import { Injectable } from "@nestjs/common"; -import { JwtService, JwtSignOptions } from "@nestjs/jwt"; -import { ConfigService } from "@nestjs/config"; -import { CacheService } from "../../common/cache/cache.service"; -import { UnauthorizedException } from "@nestjs/common/exceptions"; -import { UserService } from "./user.service"; -import { UserDto } from "../presentation/user.dto"; -import { AuthDto } from "../presentation/auth.dto"; +import { Injectable, Logger } from '@nestjs/common'; +import { JwtService, JwtSignOptions } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; +import { CacheService } from '../../common/cache/cache.service'; +import { UnauthorizedException } from '@nestjs/common/exceptions'; +import { UserService } from './user.service'; +import { UserDto } from '../presentation/user.dto'; +import { AuthDto } from '../presentation/auth.dto'; @Injectable() export class AuthService { private readonly accessTokenOption: JwtSignOptions; private readonly refreshTokenOption: JwtSignOptions; private readonly accessTokenStrategy: string; + private readonly logger = new Logger(AuthService.name); constructor( private readonly jwtService: JwtService, @@ -29,18 +30,25 @@ export class AuthService { expiresIn: this.configService.get('jwt/refresh/expire'), }; - this.accessTokenStrategy = this.configService.get( - 'jwt/access/strategy', + this.accessTokenStrategy = ( + this.configService.get('jwt/access/strategy') ); } async login(dto: UserDto): Promise { let user: UserDto = await this.userService.findOne(dto); user = user ?? (await this.userService.create(dto)); + user.userId = user.customerId ?? user.driverId ?? user.businessId; - user.userId = user['customerId'] ?? user['driverId'] ?? user['businessId']; user.userType = dto.userType; + if (!user.userType) { + throw new UnauthorizedException('사용자 타입이 없습니다.'); + } + if (!user.userId) { + throw new UnauthorizedException('사용자 아이디가 없습니다.'); + } + const accessToken = this.jwtService.sign( { tokenType: 'access', @@ -79,11 +87,23 @@ export class AuthService { }; } - async tokenRefresh(request: Request): Promise { - const token = request.headers['authorization'].replace('Bearer ', ''); + async tokenRefresh( + request: Request & { headers: { authorization?: string } }, + ): Promise { + const token = request.headers.authorization?.replace('Bearer ', ''); + + if (!token) { + throw new UnauthorizedException('Authorization 헤더에 토큰이 없습니다.'); + } const payload = this.jwtService.decode(token); + console.log('payload', payload); + + if (!payload) { + throw new UnauthorizedException('토큰이 유효하지 않습니다.'); + } + const user: UserDto = await this.userService.findOne({ userType: payload.userType, userId: payload.subject, @@ -101,7 +121,7 @@ export class AuthService { const refreshToken = this.jwtService.sign( { tokenType: 'refresh', - subject: user.userId, + subject: user.userId!, userType: user.userType, }, this.refreshTokenOption, @@ -124,6 +144,11 @@ export class AuthService { } private async saveAccessToken(user: UserDto, accessToken: string) { + if (!user.userType && !user.userId) { + this.logger.error('사용자 정보가 없습니다.'); + throw new UnauthorizedException('사용자 정보가 없습니다.'); + } + const key = `${user.userType}:${user.userId}:accessToken`; if (this.accessTokenStrategy?.toLowerCase() === 'unique') { @@ -157,7 +182,7 @@ export class AuthService { async getUser(token: string): Promise { const payload = await this.jwtService.verify(token); if (!payload) { - throw new UnauthorizedException(); + throw new UnauthorizedException('토큰이 유효하지 않습니다.'); } return await this.userService.findOne({ diff --git a/src/auth/application/jwt-access.strategy.ts b/src/auth/application/jwt-access.strategy.ts index f6781a3..d15e411 100644 --- a/src/auth/application/jwt-access.strategy.ts +++ b/src/auth/application/jwt-access.strategy.ts @@ -22,7 +22,10 @@ export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') { }); } - async validate(req: Request, payload: any): Promise { + async validate( + req: Request & { headers: { authorization?: string } }, + payload: any, + ): Promise { if (!payload) { throw new UnauthorizedException(); } @@ -31,7 +34,11 @@ export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') { throw new BadRequestException(); } - const token = req.headers['authorization'].replace('Bearer ', ''); - return JSON.parse(await this.cacheService.get(token)) as UserDto; + const token = req.headers.authorization?.replace('Bearer ', ''); + if (!token) { + throw new UnauthorizedException('Authorization 헤더에 토큰이 없습니다.'); + } + + return JSON.parse(await this.cacheService.get(token)) as UserDto; } } diff --git a/src/auth/application/security.service.ts b/src/auth/application/security.service.ts index ed99985..8761174 100644 --- a/src/auth/application/security.service.ts +++ b/src/auth/application/security.service.ts @@ -9,12 +9,14 @@ export class SecurityService { private iv = crypto.randomBytes(16); // 초기화 벡터(IV) constructor(private readonly configService: ConfigService) { - this.algorithm = this.configService.get('security/crypto/algorithm'); - this.secretKey = this.configService.get('security/crypto/key'); + this.algorithm = ( + this.configService.get('security/crypto/algorithm') + ); + this.secretKey = this.configService.get('security/crypto/key'); } - encrypt(text: string): string { - if (!text) return null; + encrypt(text: string): string | undefined { + if (!text) return undefined; const cipher = crypto.createCipheriv( this.algorithm, @@ -25,13 +27,13 @@ export class SecurityService { return `${this.iv.toString('hex')}:${encrypted.toString('hex')}`; // IV와 암호화된 데이터를 함께 반환 } - decrypt(hash: string): string { - if (!hash) return null; + decrypt(hash: string): string | undefined { + if (!hash) return undefined; const [iv, encryptedText] = hash.split(':'); if (!iv || !encryptedText) { - return null; + return undefined; } const decipher = crypto.createDecipheriv( diff --git a/src/auth/application/user.service.ts b/src/auth/application/user.service.ts index 647263f..4ff8992 100644 --- a/src/auth/application/user.service.ts +++ b/src/auth/application/user.service.ts @@ -23,18 +23,18 @@ export class UserService { } async findOne(dto: UserDto): Promise { - const user = await this.userServices[dto.userType].findOne(dto); + const user = await this.userServices[dto.userType!].findOne(dto); user && (user.userType = dto.userType); return user; } async create(dto: UserDto) { - return this.userServices[dto.userType].create(dto); + return this.userServices[dto.userType!].create(dto); } async update(dto: AuthDto) { - return await this.userServices[dto.userType].update(dto); + return await this.userServices[dto.userType!].update(dto); } toUserDto(user: any) { diff --git a/src/auth/presentation/user.dto.ts b/src/auth/presentation/user.dto.ts index c0d1bc3..db3524f 100644 --- a/src/auth/presentation/user.dto.ts +++ b/src/auth/presentation/user.dto.ts @@ -59,4 +59,8 @@ export class UserDto { description: '사용자 이름입니다.', }) name?: string; + + customerId?: number; + driverId?: number; + businessId?: number; } diff --git a/src/business/application/business.service.ts b/src/business/application/business.service.ts index 05487ea..b46c84c 100644 --- a/src/business/application/business.service.ts +++ b/src/business/application/business.service.ts @@ -5,6 +5,7 @@ import { IUserService } from '../../auth/user.interface'; import { UserDto, UserType } from '../../auth/presentation/user.dto'; import { Business } from '../../schemas/business.entity'; import { AuthDto } from '../../auth/presentation/auth.dto'; +import { Builder } from 'builder-pattern'; @Injectable() export class BusinessService implements IUserService { @@ -17,21 +18,21 @@ export class BusinessService implements IUserService { ) {} async findOne(dto: Partial): Promise { - const where = {}; - - dto.userId && (where['businessId'] = dto.userId ?? dto['businessId']); - dto.uuid && (where['uuid'] = dto.uuid); - - return await this.businessRepository.findOne({ - where: where, + return await this.businessRepository.findOneOrFail({ + where: { + businessId: dto.userId, + uuid: dto.uuid, + }, }); } async create(dto: UserDto): Promise { const business = new Business(); - business.uuid = dto.uuid; - business.businessName = dto.name; - business.authProvider = dto.authProvider; + Builder() + .uuid(dto.uuid!) + .businessName(dto.name!) + .authProvider(dto.authProvider!) + .build(); return await this.businessRepository .save(this.businessRepository.create(business)) @@ -45,13 +46,12 @@ export class BusinessService implements IUserService { async update(dto: AuthDto): Promise { return this.findOne(dto).then(async (business) => { - if (business) { - business.businessName = dto.name; - business.businessPhoneNumber = dto.phoneNumber; - business.refreshToken = dto.refreshToken ?? business.refreshToken; + business.businessName = dto.name ?? business.businessName; + business.businessPhoneNumber = + dto.phoneNumber ?? business.businessPhoneNumber; + business.refreshToken = dto.refreshToken ?? business.refreshToken; - return await this.businessRepository.save(business); - } + return await this.businessRepository.save(business); }); } diff --git a/src/business/presentation/business.dto.ts b/src/business/presentation/business.dto.ts index 546b74b..0132d3a 100644 --- a/src/business/presentation/business.dto.ts +++ b/src/business/presentation/business.dto.ts @@ -21,7 +21,7 @@ export class BusinessDto extends AuthDto { @IsNumber() @IsOptional() @ValidateIf((o) => !o.uuid, { groups: CRUD }) - businessId?: number; + override businessId?: number; @ApiProperty({ description: 'ResourceServer에서 제공한 업체 식별자', @@ -32,7 +32,7 @@ export class BusinessDto extends AuthDto { @IsNotEmpty({ groups: CRUD }) @IsOptional() @ValidateIf((o) => !o.businessId, { groups: CRUD }) - uuid: string; + override uuid: string; @ApiProperty({ description: '업체 이름', @@ -96,5 +96,5 @@ export class BusinessDto extends AuthDto { @IsNotEmpty() @IsOptional() @IsEnum(AuthProvider, { groups: [UserGroup.login] }) - authProvider: AuthProvider; + override authProvider: AuthProvider; } diff --git a/src/chat/application/business-chat.service.ts b/src/chat/application/business-chat.service.ts index 12050d4..c17ce5e 100644 --- a/src/chat/application/business-chat.service.ts +++ b/src/chat/application/business-chat.service.ts @@ -1,11 +1,11 @@ -import { Injectable, Logger } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Repository } from "typeorm"; -import { BusinessChatRoom } from "../../schemas/business-chat-room.entity"; -import { IChatService } from "./chat.interface"; -import { UserDto } from "../../auth/presentation/user.dto"; -import { ChatRoomDto } from "../presentation/chat.dto"; -import { toDto } from "../../common/function/util.function"; +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { BusinessChatRoom } from '../../schemas/business-chat-room.entity'; +import { IChatService } from './chat.interface'; +import { UserDto } from '../../auth/presentation/user.dto'; +import { ChatRoomDto } from '../presentation/chat.dto'; +import { toDto } from '../../common/function/util.function'; @Injectable() export class BusinessChatService implements IChatService { @@ -40,7 +40,7 @@ export class BusinessChatService implements IChatService { businessId: number, chatRoomId: number, ): Promise { - return await this.businessChatRoomRepository.findOne({ + return await this.businessChatRoomRepository.findOneOrFail({ where: { businessId, chatRoomId }, }); } diff --git a/src/chat/application/chat.service.ts b/src/chat/application/chat.service.ts index 33b5159..4803992 100644 --- a/src/chat/application/chat.service.ts +++ b/src/chat/application/chat.service.ts @@ -1,21 +1,21 @@ -import { ForbiddenException, Injectable, Logger } from "@nestjs/common"; -import { Repository } from "typeorm"; -import { ChatRoom } from "../../schemas/chat-room.entity"; -import { InjectRepository } from "@nestjs/typeorm"; -import { ChatMessage } from "../../schemas/chat-message.entity"; -import { CustomerChatService } from "./customer-chat.service"; -import { DriverChatService } from "./driver-chat.service"; -import { BusinessChatService } from "./business-chat.service"; -import { ChatMessageDto, ChatRoomDto } from "../presentation/chat.dto"; -import { UserDto, UserType } from "../../auth/presentation/user.dto"; -import { UserSocket } from "../presentation/chat.gateway"; -import { CacheService } from "../../common/cache/cache.service"; -import { Customer } from "../../schemas/customer.entity"; -import { Driver } from "../../schemas/drivers.entity"; -import { CursorDto } from "../../common/dto/cursor.dto"; -import { Business } from "../../schemas/business.entity"; -import { IChatService } from "./chat.interface"; -import { BadRequestException } from "@nestjs/common/exceptions"; +import { ForbiddenException, Injectable, Logger } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { ChatRoom } from '../../schemas/chat-room.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ChatMessage } from '../../schemas/chat-message.entity'; +import { CustomerChatService } from './customer-chat.service'; +import { DriverChatService } from './driver-chat.service'; +import { BusinessChatService } from './business-chat.service'; +import { ChatMessageDto, ChatRoomDto } from '../presentation/chat.dto'; +import { UserDto, UserType } from '../../auth/presentation/user.dto'; +import { UserSocket } from '../presentation/chat.gateway'; +import { CacheService } from '../../common/cache/cache.service'; +import { Customer } from '../../schemas/customer.entity'; +import { Driver } from '../../schemas/drivers.entity'; +import { CursorDto } from '../../common/dto/cursor.dto'; +import { Business } from '../../schemas/business.entity'; +import { IChatService } from './chat.interface'; +import { BadRequestException } from '@nestjs/common/exceptions'; @Injectable() export class ChatService { @@ -39,34 +39,34 @@ export class ChatService { // 사용자 채팅방 목록 async findChatRooms(user: UserDto): Promise { - return await this.roomServices.get(user.userType).findChatRooms(user); + return await this.roomServices.get(user.userType!)!.findChatRooms(user); } // 유저가 채팅방에 존재하는지 확인 async exitsUserChatRoom(user: UserDto, chatRoomId: number): Promise { - return this.roomServices.get(user.userType).exitsUserRoom(user, chatRoomId); + return this.roomServices + .get(user.userType!)! + .exitsUserRoom(user, chatRoomId); } // 채팅방 존재 유무 async exists(chatRoom: Partial): Promise { - const where = {}; - - chatRoom.chatRoomId && (where['chatRoomId'] = chatRoom.chatRoomId); - chatRoom.tsid && (where['tsid'] = chatRoom.tsid); - return this.chatRepository.exists({ - where, + where: { + chatRoomId: chatRoom.chatRoomId, + tsid: chatRoom.tsid, + }, }); } // 특정 채팅방 조회 async findOne(chatRoom: Partial): Promise { - const where = {}; - - chatRoom.chatRoomId && (where['chatRoomId'] = chatRoom.chatRoomId); - chatRoom.tsid && (where['tsid'] = chatRoom.tsid); - - return await this.chatRepository.findOneOrFail({ where }); + return await this.chatRepository.findOneOrFail({ + where: { + chatRoomId: chatRoom.chatRoomId, + tsid: chatRoom.tsid, + }, + }); } async createChatRoom( @@ -82,7 +82,7 @@ export class ChatService { ); dto.chatRoomId = newRoom.chatRoomId; - await this.roomServices.get(dto.inviteUser.userType)!.createChatRoom(dto); + await this.roomServices.get(dto.inviteUser.userType!)!.createChatRoom(dto); dto.inviteUser.userId = customer.customerId; await this.customerChatService.createChatRoom(dto); @@ -92,7 +92,7 @@ export class ChatService { roomDto.tsid = newRoom.tsid; roomDto.chatRoomName = newRoom.chatRoomName; roomDto.inviteUser = dto.inviteUser; - roomDto.lastMessage = null; + roomDto.lastMessage = undefined; roomDto.createdAt = newRoom.createdAt; return roomDto; } @@ -167,12 +167,9 @@ export class ChatService { return { data: chatMessages.map((message) => { - const userType: UserType = message['customer'] - ? 'customer' - : message['driver'] - ? 'driver' - : 'business'; + const userType: UserType = 'customer'; + // @ts-ignore const user = message[userType]; return { diff --git a/src/chat/application/http-to-socket-exception.filter.ts b/src/chat/application/http-to-socket-exception.filter.ts index 2935e3b..0884528 100644 --- a/src/chat/application/http-to-socket-exception.filter.ts +++ b/src/chat/application/http-to-socket-exception.filter.ts @@ -7,14 +7,14 @@ export class HttpToSocketExceptionFilter extends BaseWsExceptionFilter { v.forEach((chatRoomId) => { this.chatService.join(client, chatRoomId); @@ -87,8 +91,9 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { throw new NotFoundException(`Room ${dto.chatRoomId} not found`); } + const user = client.user; const userChatRoomExsits = await this.chatService.exitsUserChatRoom( - client.user, + user, dto.chatRoomId, ); @@ -142,7 +147,7 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { getUser(client: UserSocket): UserDto { const user = client.user; if (!user) { - throw new UnauthorizedException('Unauthorized user'); + throw new UnauthorizedException('유저 정보가 없습니다.'); } return user; diff --git a/src/common/broker/consumer/consumer.module.ts b/src/common/broker/consumer/consumer.module.ts index b2e8a78..320ba08 100644 --- a/src/common/broker/consumer/consumer.module.ts +++ b/src/common/broker/consumer/consumer.module.ts @@ -3,18 +3,21 @@ import { ConfigService } from '@nestjs/config'; import { SqsModule } from '@ssut/nestjs-sqs'; import { SQSClient } from '@aws-sdk/client-sqs'; import { sqsName } from '../../image/application/image.consumer'; +import { SqsOptions } from '@ssut/nestjs-sqs/dist/sqs.types'; @Module({ imports: [ SqsModule.registerAsync({ inject: [ConfigService], - useFactory: (configService: ConfigService) => { + useFactory: (configService: ConfigService): SqsOptions => { const sqsClient = new SQSClient({ - region: configService.get('AWS_REGION'), + region: configService.get('AWS_REGION'), credentials: { - accessKeyId: configService.get('AWS_IAM_ACCESS_KEY_ID'), - secretAccessKey: configService.get( - 'AWS_IAM_SECRET_ACCESS_KEY', + accessKeyId: ( + configService.get('AWS_IAM_ACCESS_KEY_ID') + ), + secretAccessKey: ( + configService.get('AWS_IAM_SECRET_ACCESS_KEY') ), }, }); @@ -23,10 +26,10 @@ import { sqsName } from '../../image/application/image.consumer'; consumers: [ { name: 's3-image-object-created', - queueUrl: configService.get( - `sqs/url/${sqsName.s3ImageCreated}`, + queueUrl: ( + configService.get(`sqs/url/${sqsName.s3ImageCreated}`) ), - region: configService.get('AWS_REGION'), + region: configService.get('AWS_REGION'), sqs: sqsClient, }, ], diff --git a/src/common/cache/cache.service.ts b/src/common/cache/cache.service.ts index 4ce630c..ff3a7e8 100644 --- a/src/common/cache/cache.service.ts +++ b/src/common/cache/cache.service.ts @@ -11,11 +11,11 @@ export class CacheService { this.redis = this.redisService.getClient(); } - async get(key: string): Promise { + async get(key: string): Promise { return this.redis.get(`${this.prefix}${key}`); } - async getData(key: string): Promise { + async getData(key: string): Promise { const obj = await this.redis.get(`${this.prefix}${key}`); return obj ? (JSON.parse(obj) as T) : undefined; } diff --git a/src/common/cloud/aws/s3/application/s3.service.ts b/src/common/cloud/aws/s3/application/s3.service.ts index d42e27f..adc9158 100644 --- a/src/common/cloud/aws/s3/application/s3.service.ts +++ b/src/common/cloud/aws/s3/application/s3.service.ts @@ -1,17 +1,18 @@ -import { Injectable } from "@nestjs/common"; -import { S3 } from "aws-sdk"; -import { ConfigService } from "@nestjs/config"; -import { BadRequestException } from "@nestjs/common/exceptions"; -import { ICloudStorage } from "../../../cloud-storage.interface"; -import { ImageMetaDataDto } from "../../../../image/presentation/image.dto"; -import { PresignedUrlDto } from "../presentation/presigned-url.dto"; +import { Injectable } from '@nestjs/common'; +import { S3 } from 'aws-sdk'; +import { ConfigService } from '@nestjs/config'; +import { BadRequestException } from '@nestjs/common/exceptions'; +import { ICloudStorage } from '../../../cloud-storage.interface'; +import { ImageMetaDataDto } from '../../../../image/presentation/image.dto'; +import { PresignedUrlDto } from '../presentation/presigned-url.dto'; @Injectable() export class S3Service implements ICloudStorage { private readonly s3Client: S3; - private readonly bucketName: string = - this.configService.get('s3/bucket_name'); - private readonly env: string = this.configService.get('NODE_ENV'); + private readonly bucketName: string = ( + this.configService.get('s3/bucket_name') + ); + private readonly env: string = this.configService.get('NODE_ENV'); private readonly supportExtensions: string[] = [ 'png', 'jpg', diff --git a/src/common/cloud/aws/s3/presentation/presigned-url.dto.ts b/src/common/cloud/aws/s3/presentation/presigned-url.dto.ts index eda68ba..7b465a6 100644 --- a/src/common/cloud/aws/s3/presentation/presigned-url.dto.ts +++ b/src/common/cloud/aws/s3/presentation/presigned-url.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { ImageMetaDataDto } from "../../../../image/presentation/image.dto"; +import { ApiProperty } from '@nestjs/swagger'; +import { ImageMetaDataDto } from '../../../../image/presentation/image.dto'; export class PresignedUrlDto extends ImageMetaDataDto { @ApiProperty({ @@ -11,5 +11,5 @@ export class PresignedUrlDto extends ImageMetaDataDto { 'presignedUrl은 유효시간이 따로 존재합니다.\n' + '해당 시간이 초과된 경우 403 Code를 반환합니다.', }) - expiredTime: number; + override expiredTime: number; } diff --git a/src/common/image/application/image.consumer.ts b/src/common/image/application/image.consumer.ts index e018e58..3597774 100644 --- a/src/common/image/application/image.consumer.ts +++ b/src/common/image/application/image.consumer.ts @@ -1,13 +1,14 @@ -import { Injectable, Logger } from "@nestjs/common"; -import { SqsConsumerEventHandler, SqsMessageHandler } from "@ssut/nestjs-sqs"; -import { Message } from "aws-sdk/clients/sqs"; -import { S3EventDetailDto } from "../../cloud/aws/sqs/presentation/s3-image-created-event-message.dto"; -import { ImageService } from "./image.service"; -import { Image } from "../../../schemas/image.entity"; -import { validateOrReject as validation } from "class-validator"; -import { ConfigService } from "@nestjs/config"; -import { SQS } from "aws-sdk"; -import { toDto } from "../../function/util.function"; +import { Injectable, Logger } from '@nestjs/common'; +import { SqsConsumerEventHandler, SqsMessageHandler } from '@ssut/nestjs-sqs'; +import { Message } from 'aws-sdk/clients/sqs'; +import { S3EventDetailDto } from '../../cloud/aws/sqs/presentation/s3-image-created-event-message.dto'; +import { ImageService } from './image.service'; +import { Image } from '../../../schemas/image.entity'; +import { validateOrReject as validation } from 'class-validator'; +import { ConfigService } from '@nestjs/config'; +import { SQS } from 'aws-sdk'; +import { toDto } from '../../function/util.function'; +import { BadRequestException } from '@nestjs/common/exceptions'; export const sqsName = { s3ImageCreated: 's3-image-object-created', @@ -16,20 +17,22 @@ export const sqsName = { @Injectable() export class ImageConsumer { private readonly logger: Logger = new Logger(ImageConsumer.name); - private readonly sqs: SQS = new SQS({ - region: this.configService.get('AWS_REGION'), - credentials: { - accessKeyId: this.configService.get('AWS_IAM_ACCESS_KEY_ID'), - secretAccessKey: this.configService.get( - 'AWS_IAM_SECRET_ACCESS_KEY', - ), - }, - }); + private readonly sqs: SQS; constructor( private readonly imageService: ImageService, private readonly configService: ConfigService, - ) {} + ) { + this.sqs = new SQS({ + region: this.configService.get('AWS_REGION'), + credentials: { + accessKeyId: this.configService.get('AWS_IAM_ACCESS_KEY_ID')!, + secretAccessKey: this.configService.get( + 'AWS_IAM_SECRET_ACCESS_KEY', + )!, + }, + }); + } @SqsMessageHandler(sqsName.s3ImageCreated, false) public async consumeMessage(message: Message): Promise { @@ -37,14 +40,23 @@ export class ImageConsumer { if (!message.Body) { this.logger.warn(`Message body is empty`); - return; + throw new BadRequestException('Message body is empty'); } const s3Event = toDto(S3EventDetailDto, JSON.parse(message.Body)); await validation(s3Event); const imageKey = s3Event.detail.object.key; - const uuid = imageKey.match(/images\/([^/]+)\//)[1]; + const match = imageKey.match(/images\/([^/]+)\//); + + if (!match) { + this.logger.warn(`Image key does not match the expected pattern`); + throw new BadRequestException( + 'Image key does not match the expected pattern', + ); + } + + const uuid = match[1]; return await this.imageService .create({ @@ -61,8 +73,8 @@ export class ImageConsumer { private deleteMessage(queueName: string, message: SQS.Message) { this.sqs.deleteMessage( { - QueueUrl: this.configService.get(`sqs/url/${queueName}`), - ReceiptHandle: message.ReceiptHandle, + QueueUrl: this.configService.get(`sqs/url/${queueName}`), + ReceiptHandle: message.ReceiptHandle!, }, (err, data) => { if (err) { diff --git a/src/common/validation/validation.decorator.ts b/src/common/validation/validation.decorator.ts index 295b567..a113c24 100644 --- a/src/common/validation/validation.decorator.ts +++ b/src/common/validation/validation.decorator.ts @@ -1,10 +1,11 @@ import { applyDecorators, UsePipes, ValidationPipe } from '@nestjs/common'; import { Group, ValidationDefaultOption } from './validation.data'; +import { ValidationOptions } from 'class-validator'; // Socket은 ValidationPipe와 Filter가 글로벌 적용되지 않기 때문에 Subscribe 데코레이터를 만들어서 사용한다. export function GroupValidation(groups?: Group[]) { - const option = { ...ValidationDefaultOption }; + const option: ValidationOptions = { ...ValidationDefaultOption }; option.groups = groups; return applyDecorators(UsePipes(new ValidationPipe(option))); } diff --git a/src/config/logger/logger.config.ts b/src/config/logger/logger.config.ts index 60cb049..63033a2 100644 --- a/src/config/logger/logger.config.ts +++ b/src/config/logger/logger.config.ts @@ -16,15 +16,15 @@ export class LoggerService extends ConsoleLogger { super(); } - debug(message: any, ...optionalParams: any[]) { + override debug(message: any, ...optionalParams: any[]) { super.debug(`🐛 ${message}`, ...optionalParams); } - log(message: any, ...optionalParams: any[]) { + override log(message: any, ...optionalParams: any[]) { super.log(`🪵 ${message}`, ...optionalParams); } - warn(message: any, ...optionalParams: any[]) { + override warn(message: any, ...optionalParams: any[]) { super.warn(`⚠️ ${message}`, ...optionalParams); this.cacheCheck('warn', message) && @@ -35,7 +35,7 @@ export class LoggerService extends ConsoleLogger { ); } - error(message: any, ...optionalParams: any[]) { + override error(message: any, ...optionalParams: any[]) { super.error(`💥 ${message}`, ...optionalParams); this.cacheCheck('error', message) && @@ -62,8 +62,8 @@ export class LoggerService extends ConsoleLogger { const cacheKey = `${logLevel}:${message}`; const now = Date.now(); - if (this.logCache.has(cacheKey)) { - const lastSent = this.logCache.get(cacheKey); + const lastSent = this.logCache.get(cacheKey); + if (lastSent) { if (now - lastSent < this.cacheDuration) { // 동일한 메시지가 캐시 지속 시간 내에 이미 전송된 경우 전송 생략 return false; diff --git a/src/config/socket/socket.adapter.ts b/src/config/socket/socket.adapter.ts index dc407d3..9793ff5 100644 --- a/src/config/socket/socket.adapter.ts +++ b/src/config/socket/socket.adapter.ts @@ -22,7 +22,7 @@ export class RedisIoAdapter extends IoAdapter { this.adapterConstructor = createAdapter({ pubClient, subClient }); } - createIOServer(port: number, options?: any): any { + override createIOServer(port: number, options?: any): any { const server = super.createIOServer(port, options); server.adapter(this.adapterConstructor); return server; diff --git a/src/customer/application/customer.service.ts b/src/customer/application/customer.service.ts index fed9ac7..eeacc0b 100644 --- a/src/customer/application/customer.service.ts +++ b/src/customer/application/customer.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Customer } from '../../schemas/customer.entity'; @@ -10,6 +10,7 @@ import { SecurityService } from '../../auth/application/security.service'; import { Image } from '../../schemas/image.entity'; import { ImageService } from '../../common/image/application/image.service'; import { toDto } from '../../common/function/util.function'; +import { BadRequestException } from '@nestjs/common/exceptions'; @Injectable() export class CustomerService implements IUserService { @@ -24,6 +25,7 @@ export class CustomerService implements IUserService { ) {} async create(dto: CustomerDto): Promise { + this.logger.log(`Create new customer id:${dto.customerId}`); return await this.customerRepository.save( this.customerRepository.create(dto), ); @@ -33,6 +35,10 @@ export class CustomerService implements IUserService { dto: Partial, encrypt: boolean = false, ): Promise { + if (dto.userId && dto.uuid) { + throw new BadRequestException('식별할 수 없는 사용자입니다.'); + } + const query = this.customerRepository .createQueryBuilder('C') .leftJoinAndMapOne('C.profileImage', Image, 'I', 'C.uuid = I.uuid') @@ -40,7 +46,7 @@ export class CustomerService implements IUserService { if (dto.userId) { query.andWhere('C.customer_id = :customer_id', { - customer_id: dto.userId, + customer_id: dto.userId ?? dto.customerId, }); } @@ -53,18 +59,28 @@ export class CustomerService implements IUserService { const customer = await query.getOne(); - if (encrypt) { - customer.customerPhoneNumber = this.securityService.decrypt( - customer.customerPhoneNumber, - ); - - customer.customerAddress = this.securityService.decrypt( - customer.customerAddress, - ); + if (!customer) { + throw new NotFoundException('존재하지 않는 사용자입니다.'); + } - customer.customerDetailAddress = this.securityService.decrypt( - customer.customerDetailAddress, - ); + if (encrypt && customer) { + if (customer?.customerPhoneNumber) { + customer.customerPhoneNumber = this.securityService.decrypt( + customer?.customerPhoneNumber, + ); + } + + if (customer?.customerAddress) { + customer.customerAddress = this.securityService.decrypt( + customer.customerAddress, + ); + } + + if (customer?.customerDetailAddress) { + customer.customerDetailAddress = this.securityService.decrypt( + customer.customerDetailAddress, + ); + } } return customer; @@ -72,9 +88,9 @@ export class CustomerService implements IUserService { async update(dto: Partial): Promise { if (dto.phoneNumber || dto.customerPhoneNumber) { - dto.phoneNumber = this.securityService.encrypt( - dto.phoneNumber ?? dto.customerPhoneNumber, - ); + const phoneNumber = dto.phoneNumber ?? dto.customerPhoneNumber; + + dto.phoneNumber = this.securityService.encrypt(phoneNumber!); } if (dto.customerDetailAddress) { @@ -89,18 +105,16 @@ export class CustomerService implements IUserService { return await this.findOne(dto) .then(async (customer) => { - if (customer) { - customer.customerName = dto.customerName ?? customer.customerName; - customer.customerPhoneNumber = - dto.phoneNumber ?? customer.customerPhoneNumber; - customer.customerAddress = - dto.customerAddress ?? customer.customerAddress; - customer.customerDetailAddress = - dto.customerDetailAddress ?? customer.customerDetailAddress; - customer.refreshToken = dto.refreshToken ?? customer.refreshToken; - - return await this.customerRepository.save(customer); - } + customer.customerName = dto.customerName ?? customer.customerName; + customer.customerPhoneNumber = + dto.phoneNumber ?? customer.customerPhoneNumber; + customer.customerAddress = + dto.customerAddress ?? customer.customerAddress; + customer.customerDetailAddress = + dto.customerDetailAddress ?? customer.customerDetailAddress; + customer.refreshToken = dto.refreshToken ?? customer.refreshToken; + + return await this.customerRepository.save(customer); }) .then(async (customer) => { if (customer && dto.presignedUrlDto) { @@ -113,7 +127,7 @@ export class CustomerService implements IUserService { customerDto.presignedUrlDto = presignedUrlDto.find(() => true); // The first truthy vo (!undefined, !null ...) return customerDto; } - return customer; + return customer!; }); } diff --git a/src/customer/presentation/customer.controller.ts b/src/customer/presentation/customer.controller.ts index e071e48..699fcbd 100644 --- a/src/customer/presentation/customer.controller.ts +++ b/src/customer/presentation/customer.controller.ts @@ -1,9 +1,10 @@ -import { CustomerService } from "../application/customer.service"; -import { Body, Controller, Get, Put } from "@nestjs/common"; -import { CustomerDto } from "./customer.dto"; -import { Customer } from "../../schemas/customer.entity"; -import { Auth, CurrentCustomer } from "../../auth/decorator/auth.decorator"; -import { ApiOkResponse, ApiOperation, ApiTags } from "@nestjs/swagger"; +import { CustomerService } from '../application/customer.service'; +import { Body, Controller, Get, Put } from '@nestjs/common'; +import { CustomerDto } from './customer.dto'; +import { Customer } from '../../schemas/customer.entity'; +import { Auth, CurrentCustomer } from '../../auth/decorator/auth.decorator'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Builder } from 'builder-pattern'; @ApiTags('고객 관련 API') @Controller('/v1/customer') @@ -23,12 +24,20 @@ export class CustomerController { return await this.customerService .findOne({ userId: customer.customerId }, true) .then((v) => { - delete v['accessToken']; - delete v['refreshToken']; - return { - ...v, - profileImageUrl: v?.profileImage?.imageUrl, - }; + return Builder() + .uuid(v.uuid) + .userType('customer') + .userId(v.customerId) + .name(v.customerName) + .customerId(v.customerId) + .customerName(v.customerName) + .customerPhoneNumber(v.customerPhoneNumber) + .customerLocation(v.customerLocation) + .customerAddress(v.customerAddress) + .customerDetailAddress(v.customerDetailAddress) + .authProvider(v.authProvider) + .profileImageUrl(v?.profileImage?.imageUrl) + .build(); }); } diff --git a/src/customer/presentation/customer.dto.ts b/src/customer/presentation/customer.dto.ts index b8579e7..2f71d3b 100644 --- a/src/customer/presentation/customer.dto.ts +++ b/src/customer/presentation/customer.dto.ts @@ -1,9 +1,17 @@ -import { IsEnum, IsNotEmpty, IsNumber, IsOptional, Length, Matches, ValidateNested } from "class-validator"; -import { Point } from "typeorm"; -import { ApiProperty } from "@nestjs/swagger"; -import { AuthDto } from "../../auth/presentation/auth.dto"; -import { AuthProvider } from "../../auth/presentation/user.dto"; -import { PresignedUrlDto } from "../../common/cloud/aws/s3/presentation/presigned-url.dto"; +import { + IsEnum, + IsNotEmpty, + IsNumber, + IsOptional, + Length, + Matches, + ValidateNested, +} from 'class-validator'; +import { Point } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger'; +import { AuthDto } from '../../auth/presentation/auth.dto'; +import { AuthProvider } from '../../auth/presentation/user.dto'; +import { PresignedUrlDto } from '../../common/cloud/aws/s3/presentation/presigned-url.dto'; export class CustomerDto extends AuthDto { @ApiProperty({ @@ -13,7 +21,7 @@ export class CustomerDto extends AuthDto { }) @IsNumber() @IsOptional() - customerId?: number; + override customerId?: number; @ApiProperty({ description: 'ResourceServer에서 제공한 유저 식별자', @@ -22,7 +30,7 @@ export class CustomerDto extends AuthDto { }) @IsNotEmpty() @Length(1, 44) - uuid: string; + override uuid: string; @ApiProperty({ description: '고객 이름', @@ -76,7 +84,7 @@ export class CustomerDto extends AuthDto { }) @IsNotEmpty() @IsEnum(AuthProvider) - authProvider: AuthProvider; + override authProvider: AuthProvider; @ApiProperty({ description: '프로필 이미지 URL', diff --git a/src/driver/application/driver.service.ts b/src/driver/application/driver.service.ts index c51f858..f2eb87c 100644 --- a/src/driver/application/driver.service.ts +++ b/src/driver/application/driver.service.ts @@ -23,14 +23,12 @@ export class DriverService implements IUserService { } async findOne(dto: Partial): Promise { - const where = {}; - - dto.userId && (where['driverId'] = dto.userId); - dto.uuid && (where['uuid'] = dto.uuid); - dto.refreshToken && (where['refreshToken'] = dto.refreshToken); - - return await this.driverRepository.findOne({ - where: where, + return await this.driverRepository.findOneOrFail({ + where: { + driverId: dto.userId, + uuid: dto.uuid, + refreshToken: dto.refreshToken, + }, }); } @@ -47,13 +45,15 @@ export class DriverService implements IUserService { async update(dto: AuthDto): Promise { return this.findOne(dto).then(async (driver) => { - if (driver) { + if (dto.name) { driver.driverName = dto.name; + } + if (dto.phoneNumber) { driver.driverPhoneNumber = dto.phoneNumber; - driver.refreshToken = dto.refreshToken ?? driver.refreshToken; - - return await this.driverRepository.save(driver); } + driver.refreshToken = dto.refreshToken ?? driver.refreshToken; + + return await this.driverRepository.save(driver); }); } diff --git a/src/email/email.service.ts b/src/email/email.service.ts index e7b8fe5..8095773 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -3,6 +3,7 @@ import * as nodemailer from 'nodemailer'; import * as AWS from 'aws-sdk'; import { ConfigService } from '@nestjs/config'; import { SystemAlarmService } from '../system/system.alarm.service'; +import { BadRequestException } from '@nestjs/common/exceptions'; @Injectable() export class EmailService { @@ -12,11 +13,19 @@ export class EmailService { private readonly configService: ConfigService, private readonly systemAlarmService: SystemAlarmService, ) { + const region = this.configService.get('AWS_REGION'); + const accessKeyId = this.configService.get('AWS_IAM_ACCESS_KEY_ID'); + const secretAccessKey = this.configService.get('AWS_IAM_SECRET_ACCESS_KEY'); + + if (!region || !accessKeyId || !secretAccessKey) { + throw new BadRequestException('AWS Config is not set'); + } + const ses = new AWS.SES({ - region: this.configService.get('AWS_REGION'), + region: region, credentials: { - accessKeyId: this.configService.get('AWS_IAM_ACCESS_KEY_ID'), - secretAccessKey: this.configService.get('AWS_IAM_SECRET_ACCESS_KEY'), + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, }, }); diff --git a/src/env/ssm-config.service.ts b/src/env/ssm-config.service.ts index b49440b..432100b 100644 --- a/src/env/ssm-config.service.ts +++ b/src/env/ssm-config.service.ts @@ -17,35 +17,43 @@ export default class SSMConfigService { private readonly prefix: string = `/mgmg/server/${process.env.NODE_ENV}/`; constructor(private readonly configService: ConfigService) { + const region = this.configService.get('AWS_REGION'); + const accessKeyId = this.configService.get('AWS_IAM_ACCESS_KEY_ID'); + const secretAccessKey = this.configService.get('AWS_IAM_SECRET_ACCESS_KEY'); + + if (!region || !accessKeyId || !secretAccessKey) { + throw new Error('AWS Config is not set'); + } + this.ssmClientConfig = { - region: this.configService.get('AWS_REGION'), + region: region, credentials: { - accessKeyId: this.configService.get('AWS_IAM_ACCESS_KEY_ID'), - secretAccessKey: this.configService.get('AWS_IAM_SECRET_ACCESS_KEY'), + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, }, }; this.ssmClient = new SSMClient(this.ssmClientConfig); } - async initEnvironmentValues(): Promise { - const parameters = await this.ssmClient - .send( - new GetParametersByPathCommand({ - Path: this.prefix, - }), - ) - .then((v) => { - return v.Parameters; - }); - - for (const parameter of parameters) { - const split: string[] = parameter.Name.split('/'); - this.configService.set(split[split.length - 1], parameter.Value); - } - } - - async getParameter(parameterName: string): Promise { + // async initEnvironmentValues(): Promise { + // const parameters = await this.ssmClient + // .send( + // new GetParametersByPathCommand({ + // Path: this.prefix, + // }), + // ) + // .then((v) => { + // return v.Parameters; + // }); + // + // for (const parameter of parameters) { + // const split: string[] = parameter.Name.split('/'); + // this.configService.set(split[split.length - 1], parameter.Value); + // } + // } + + async getParameter(parameterName: string): Promise { const value = this.configService.get(parameterName); if (value) { return value; @@ -59,7 +67,7 @@ export default class SSMConfigService { return this.ssmClient .send(new GetParameterCommand(params)) .then((response) => { - return response.Parameter.Value; + return response?.Parameter?.Value; }) .catch((error) => { this.logger.error(error); @@ -75,15 +83,22 @@ export default class SSMConfigService { export const loadParameterStoreValue = async () => { const regex = /\/mgmg\/server\/[^/]*\//; - const nodeEnv = process.env.NODE_ENV; let nextToken: string | undefined; const parameters: Parameter[] = []; + const region = process.env.AWS_REGION; + const nodeEnv = process.env.NODE_ENV; + const accessKeyId = process.env.AWS_IAM_ACCESS_KEY_ID; + const secretAccessKey = process.env.AWS_IAM_SECRET_ACCESS_KEY; + + if (!region || !accessKeyId || !secretAccessKey) { + throw new Error('AWS Config is not set'); + } const ssmClient = new SSMClient({ - region: process.env.AWS_REGION, + region: region, credentials: { - accessKeyId: process.env.AWS_IAM_ACCESS_KEY_ID, - secretAccessKey: process.env.AWS_IAM_SECRET_ACCESS_KEY, + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, }, }); @@ -98,7 +113,7 @@ export const loadParameterStoreValue = async () => { }), ); - parameters.push(...response.Parameters); + parameters.push(...response.Parameters!); nextToken = response.NextToken; } while (nextToken); @@ -111,12 +126,12 @@ export const loadParameterStoreValue = async () => { return 3; }; - return getWeight(b.Name) - getWeight(a.Name); + return getWeight(b!.Name!) - getWeight(a.Name!); }); for (const parameter of parameters) { const name = parameter.Name; - const key = name.replace(regex, ''); + const key = name!.replace(regex, ''); key.indexOf('/') === 0 ? (process.env[key.substring(1)] = parameter.Value) diff --git a/src/pet/application/pet.service.ts b/src/pet/application/pet.service.ts index 870cd62..afd8294 100644 --- a/src/pet/application/pet.service.ts +++ b/src/pet/application/pet.service.ts @@ -112,7 +112,7 @@ export class PetService { async findCheckList( category: PetChecklistCategory, type: ChecklistType, - petId: number, + petId: number | null, customer: Customer, ): Promise { let query = this.petChecklistRepository @@ -202,17 +202,17 @@ export class PetService { const answer = dto.find((d) => d.petChecklistId === v.petChecklistId); if (v.petChecklistType === ChecklistType.ANSWER) { - if (!answer.petChecklistAnswer) { + if (!answer?.petChecklistAnswer) { throw new BadRequestException('답변을 적어주세요'); } await this.petChecklistAnswerRepository.save({ pet, petChecklistId: v.petChecklistId, - petChecklistAnswer: answer.petChecklistAnswer, + petChecklistAnswer: answer?.petChecklistAnswer, }); } else { - if (!answer.petChecklistChoiceId || answer.checked == undefined) { + if (!answer?.petChecklistChoiceId) { throw new BadRequestException('선택지를 선택해주세요'); } diff --git a/src/pet/presentation/pet.dto.ts b/src/pet/presentation/pet.dto.ts index dc1177b..5b8511c 100644 --- a/src/pet/presentation/pet.dto.ts +++ b/src/pet/presentation/pet.dto.ts @@ -154,13 +154,13 @@ export class PetChecklistDto { description: '체크리스트 선택지입니다.', required: false, }) - petChecklistChoices: PetChecklistChoiceDto[]; + petChecklistChoices: PetChecklistChoiceDto[] | null; @ApiProperty({ description: '체크리스트 답변입니다.', required: false, }) - petChecklistAnswer: string; + petChecklistAnswer?: string | null; } export class PetChecklistAnswerDto { diff --git a/src/schemas/image.entity.ts b/src/schemas/image.entity.ts index 7be1906..7300588 100644 --- a/src/schemas/image.entity.ts +++ b/src/schemas/image.entity.ts @@ -7,7 +7,7 @@ export class Image extends HasUuid { imageId: number; @Column({ type: 'varchar', length: 44, nullable: false }) - uuid: string; + override uuid: string; @Column({ type: 'varchar', length: 100, unique: true, nullable: false }) imageUrl: string; diff --git a/src/system/matrics/application/metrics.interceptor.ts b/src/system/matrics/application/metrics.interceptor.ts index f1f7a2b..d9540e5 100644 --- a/src/system/matrics/application/metrics.interceptor.ts +++ b/src/system/matrics/application/metrics.interceptor.ts @@ -1,7 +1,13 @@ -import { MetricsService } from "./metrics.service"; -import { CallHandler, ExecutionContext, Injectable, NestInterceptor, OnModuleInit } from "@nestjs/common"; -import { Counter, Gauge, Histogram } from "prom-client"; -import { catchError, Observable, tap } from "rxjs"; +import { MetricsService } from './metrics.service'; +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, + OnModuleInit, +} from '@nestjs/common'; +import { Counter, Gauge, Histogram } from 'prom-client'; +import { catchError, Observable, tap } from 'rxjs'; @Injectable() export class MetricsInterceptor implements NestInterceptor, OnModuleInit { @@ -84,10 +90,9 @@ export class MetricsInterceptor implements NestInterceptor, OnModuleInit { }; try { - const requestSuccessTimer = - this.requestSuccessHistogram.startTimer(labels); + this.requestSuccessHistogram.startTimer(labels); + this.requestFailHistogram.startTimer(labels); - const requestFailTimer = this.requestFailHistogram.startTimer(labels); return next.handle().pipe( tap(() => { if (this.isAvailableMetricsUrl(originUrl)) { @@ -102,6 +107,8 @@ export class MetricsInterceptor implements NestInterceptor, OnModuleInit { throw err; }), ); - } catch (error) {} + } catch (error) { + return next.handle(); + } } } diff --git a/src/system/matrics/application/metrics.service.ts b/src/system/matrics/application/metrics.service.ts index b1eba10..1c4dc97 100644 --- a/src/system/matrics/application/metrics.service.ts +++ b/src/system/matrics/application/metrics.service.ts @@ -1,5 +1,5 @@ -import { Injectable, OnModuleInit } from "@nestjs/common"; -import { Counter, Histogram, register } from "prom-client"; +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { Counter, Histogram, register } from 'prom-client'; @Injectable() export class MetricsService implements OnModuleInit { @@ -63,17 +63,17 @@ export class MetricsService implements OnModuleInit { // 성공 시에 시간재기 startSuccessTimer(labels: Record): () => void { - return this.requestSuccessHistogram.startTimer(labels); + return this.requestSuccessHistogram!.startTimer(labels); } // 실패시에 시간재기 startFailTimer(labels: Record): () => void { - return this.requestFailHistogram.startTimer(labels); + return this.requestFailHistogram!.startTimer(labels); } // 실패 횟수 상승시키기 incrementFailureCounter(labels: Record) { - this.failureCounter.labels(labels).inc(1); + this.failureCounter!.labels(labels).inc(1); } // metrics 수집한거 등록하기 diff --git a/src/system/system.alarm.service.ts b/src/system/system.alarm.service.ts index 7def3a5..d099dbe 100644 --- a/src/system/system.alarm.service.ts +++ b/src/system/system.alarm.service.ts @@ -14,7 +14,7 @@ export class SystemAlarmService { constructor(private readonly configService: ConfigService) { this.logAlarmInfo = JSON.parse( - configService.get('system/alarm'), + configService.get('system/alarm'), ) as Channel; } @@ -23,11 +23,13 @@ export class SystemAlarmService { (v) => v.channel === channelName, ); - await axios.post(channel.url, { content: message }).catch((error) => { - this.logger.error( - `Error sending message to ${this.logAlarmInfo.provider}:`, - error, - ); - }); + if (channel) { + await axios.post(channel.url, { content: message }).catch((error) => { + this.logger.error( + `Error sending message to ${this.logAlarmInfo.provider}:`, + error, + ); + }); + } } } diff --git a/src/test/application/test.service.ts b/src/test/application/test.service.ts index 1a13e25..d5dad5a 100644 --- a/src/test/application/test.service.ts +++ b/src/test/application/test.service.ts @@ -25,11 +25,11 @@ export class TestService { }) .then((customer) => { return Builder() - .userId(customer.customerId) + .userId(customer!.customerId) .userType('customer') - .uuid(customer.uuid) - .name(customer.customerName) - .authProvider(customer.authProvider) + .uuid(customer!.uuid) + .name(customer!.customerName) + .authProvider(customer!.authProvider) .build(); }); } diff --git a/tsconfig.json b/tsconfig.json index 95f5641..fac19ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,9 +12,13 @@ "baseUrl": "./", "incremental": true, "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, + "strictNullChecks": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noImplicitReturns": true , + "noImplicitOverride": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } diff --git a/yarn.lock b/yarn.lock index 5ab2ebb..7a3f5fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2445,6 +2445,26 @@ "@types/range-parser" "*" "@types/send" "*" +"@types/express-serve-static-core@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz#3c9997ae9d00bc236e45c6374e84f2596458d9db" + integrity sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c" + integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/express@^4.17.17": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" @@ -2499,6 +2519,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/jsonwebtoken@*": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2" + integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg== + dependencies: + "@types/node" "*" + "@types/jsonwebtoken@9.0.5": version "9.0.5" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588" @@ -2530,6 +2557,36 @@ dependencies: undici-types "~5.26.4" +"@types/nodemailer@^6.4.16": + version "6.4.16" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.16.tgz#db006abcb1e1c8e6ea2fb53b27fefec3c03eaa6c" + integrity sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ== + dependencies: + "@types/node" "*" + +"@types/passport-jwt@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-4.0.1.tgz#080fbe934fb9f6954fb88ec4cdf4bb2cc7c4d435" + integrity sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ== + dependencies: + "@types/jsonwebtoken" "*" + "@types/passport-strategy" "*" + +"@types/passport-strategy@*": + version "0.2.38" + resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.38.tgz#482abba0b165cd4553ec8b748f30b022bd6c04d3" + integrity sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport@*": + version "1.0.16" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.16.tgz#5a2918b180a16924c4d75c31254c31cdca5ce6cf" + integrity sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A== + dependencies: + "@types/express" "*" + "@types/qs@*": version "6.9.14" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.14.tgz#169e142bfe493895287bee382af6039795e9b75b"