Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global 로깅, 단위 테스트, 액세스 토큰 재발급, DB 테이블 구현 #91

Merged
merged 13 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,772 changes: 2,394 additions & 378 deletions be/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion be/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.0",
"typeorm": "^0.3.17"
"typeorm": "^0.3.17",
"winston": "^3.11.0"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand All @@ -49,12 +50,14 @@
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^2.0.12",
"@types/winston": "^2.4.4",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"pg-mem": "^2.7.2",
"prettier": "^3.0.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
Expand Down
16 changes: 14 additions & 2 deletions be/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@ import { UserModule } from "./user/user.module";
import { TypeOrmModule } from "@nestjs/typeorm";
import { typeORMConfig } from "./configs/typeorm.config";
import { AuthModule } from "./auth/auth.module";
import { RestaurantModule } from './restaurant/restaurant.module';
import { ReviewModule } from './review/review.module';
import { CustomLoggerService } from "./custom.logger";
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LoggingInterceptor } from "./logger.interceptor";

@Module({
imports: [UserModule, TypeOrmModule.forRoot(typeORMConfig), AuthModule],
imports: [UserModule, TypeOrmModule.forRoot(typeORMConfig), AuthModule, RestaurantModule, ReviewModule],
providers: [
CustomLoggerService,
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}
export class AppModule { }
12 changes: 12 additions & 0 deletions be/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Body,
Controller,
Headers,
Post,
Expand All @@ -11,7 +12,9 @@ import {
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiBody,
} from "@nestjs/swagger";
import { RefreshTokenDto } from "./dto/refreshToken.dto";

@Controller("auth")
export class AuthController {
Expand All @@ -29,4 +32,13 @@ export class AuthController {
signin(@Headers("authorization") authorization: string) {
return this.authService.NaverAuth(authorization);
}

@Post("refresh-token")
@ApiOperation({ summary: "accessToken 재발급" })
@ApiResponse({ status: 200, description: "성공적으로 재발급됨." })
@ApiResponse({ status: 401, description: "잘못된 refresh token." })
@ApiBody({ type: RefreshTokenDto })
checkRefreshToken(@Body() refreshTokenDto: RefreshTokenDto) {
return this.authService.checkRefreshToken(refreshTokenDto.refreshToken);
}
}
4 changes: 2 additions & 2 deletions be/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { UserModule } from "src/user/user.module";
import { UserModule } from "../user/user.module";
import { JwtStrategy } from "./strategy/jwt.strategy";

@Module({
Expand All @@ -21,4 +21,4 @@ import { JwtStrategy } from "./strategy/jwt.strategy";
providers: [AuthService, JwtStrategy],
exports: [PassportModule],
})
export class AuthModule {}
export class AuthModule { }
15 changes: 13 additions & 2 deletions be/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
HttpException,
HttpStatus,
} from "@nestjs/common";
import { UserRepository } from "src/user/user.repository";
import { UserRepository } from "../user/user.repository";
import { JwtService } from "@nestjs/jwt";
import axios from "axios";

Expand All @@ -13,7 +13,7 @@ export class AuthService {
constructor(
private userRepository: UserRepository,
private jwtService: JwtService
) {}
) { }
async NaverAuth(authorization: string) {
if (!authorization) {
throw new HttpException(
Expand Down Expand Up @@ -58,4 +58,15 @@ export class AuthService {
);
}
}

async checkRefreshToken(refreshToken: string){
try {
const decoded = this.jwtService.verify(refreshToken, { secret: 'nibobnebob' });
const payload = { id: decoded.id };
const accessToken = this.jwtService.sign(payload);
return { accessToken };
} catch (err) {
throw new HttpException('Invalid refresh token', HttpStatus.UNAUTHORIZED);
}
}
}
9 changes: 9 additions & 0 deletions be/src/auth/dto/refreshToken.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ApiProperty } from "@nestjs/swagger";

export class RefreshTokenDto {
@ApiProperty({
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
description: "클라이언트가 가지고 있는 refreshToken",
})
refreshToken: string;
}
2 changes: 1 addition & 1 deletion be/src/auth/strategy/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy, ExtractJwt } from "passport-jwt";
import { UserRepository } from "src/user/user.repository";
import { UserRepository } from "../../user/user.repository";

export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private userRepository: UserRepository) {
Expand Down
55 changes: 55 additions & 0 deletions be/src/custom.logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Injectable, LoggerService } from '@nestjs/common';
import * as winston from 'winston';

@Injectable()
export class CustomLoggerService implements LoggerService {
private logger: winston.Logger;

constructor() {
this.logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ level, message, timestamp, stack = '' }) => {
return `${timestamp} ${level}: ${message} ${stack} `;
}),
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],

});

if (process.env.NODE_ENV !== 'production') {
this.logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
})
),
}));
}
}

log(message: string) {
this.logger.info(message);
}

error(message: string, trace: string) {
this.logger.error({ message, stack: trace });
}

warn(message: string) {
this.logger.warn(message);
}

debug(message: string) {
this.logger.debug(message);
}

verbose(message: string) {
this.logger.verbose(message);
}
}
41 changes: 41 additions & 0 deletions be/src/logger.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
HttpStatus
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { CustomLoggerService } from './custom.logger';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
constructor(private logger: CustomLoggerService) { }

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const { method, url } = request;
const clientIp = request.ip || request.headers['x-forwarded-for'];

return next
.handle()
.pipe(
tap(() => {
const { statusCode } = response;
const delay = Date.now() - now;
this.logger.log(`[Success] ${clientIp} ${method} ${url} ${statusCode} ${delay}ms`);
}),
catchError((error) => {
const status = error.status || HttpStatus.INTERNAL_SERVER_ERROR;
this.logger.error(`[Error] ${clientIp} ${method} ${url} ${status} ${error.message}`, error.stack);

return throwError(error);
}),
);
}

}
15 changes: 15 additions & 0 deletions be/src/restaurant/dto/restaurantInfo.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApiProperty } from "@nestjs/swagger";
import {
IsNotEmpty,
IsInt,
} from "class-validator";

export class RestaurantInfoDto {
@ApiProperty({
example: "음식점 id",
description: "The id of the restaurant",
})
@IsInt()
@IsNotEmpty()
id: number;
}
39 changes: 39 additions & 0 deletions be/src/restaurant/entities/restaurant.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity('restaurant')
export class RestaurantInfoEntity {
@PrimaryGeneratedColumn('increment')
id: number;

@Column({ type: 'varchar', length: 100 })
name: string;

@Column({
type: 'geometry',
spatialFeatureType: 'Point',
srid: 4326,
nullable: true
})
location: string;

@Column({ type: 'text', nullable: true })
address: string | null;

@Column({ type: 'varchar', length: 20, nullable: true })
category: string | null;

@Column({ type: 'int', default: 0 })
reviewCnt: number;

@Column({ type: 'varchar', length: 20, nullable: true })
phoneNumber: string | null;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;

@Column({ name: 'deleted_at', type: 'timestamp', nullable: true })
deletedAt: Date | null;

@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
4 changes: 4 additions & 0 deletions be/src/restaurant/restaurant.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Module } from '@nestjs/common';

@Module({})
export class RestaurantModule {}
56 changes: 56 additions & 0 deletions be/src/review/dto/reviewInfo.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ApiProperty } from "@nestjs/swagger";
import {
IsBoolean,
IsString,
IsNotEmpty,
IsInt,
MaxLength,
IsOptional,
MinLength
} from "class-validator";

export class ReviewInfoDto {
@ApiProperty({
example: "true",
description: "The transportation for visiting",
})
@IsBoolean()
@IsNotEmpty()
visitMethod: boolean;

@ApiProperty({ example: "0", description: "transportation Accessibility for visiting" })
@IsInt()
@IsOptional()
@MaxLength(1)
transportationAccessibility: number | null;

@ApiProperty({ example: "0", description: "condition of the restaurant's parking area" })
@IsInt()
@IsOptional()
@MaxLength(1)
parkingArea: number | null;

@ApiProperty({ example: "0", description: "The taste of the food" })
@IsInt()
@IsNotEmpty()
@MaxLength(1)
taste: number;

@ApiProperty({ example: "0", description: "The service of the restaurant" })
@IsInt()
@IsNotEmpty()
@MaxLength(1)
service: number;

@ApiProperty({ example: "0", description: "The condition of the restaurant's restroom" })
@IsInt()
@IsNotEmpty()
@MaxLength(1)
restroomtCleanliness: number;

@ApiProperty({ example: "좋았음", description: "The overallExperience about the restaurant" })
@IsString()
@IsNotEmpty()
@MinLength(20)
overallExperience: string;
}
Loading