Skip to content

Commit

Permalink
Merge branch 'feature/#240' into bug/#250
Browse files Browse the repository at this point in the history
  • Loading branch information
swkim12345 committed Nov 26, 2024
2 parents 41a4115 + a751756 commit cacfefd
Show file tree
Hide file tree
Showing 72 changed files with 2,863 additions and 411 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# vscode setting
.vscode

# remote
.remote
4 changes: 4 additions & 0 deletions packages/backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ pids

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# backup file
.backup
.bak
13 changes: 9 additions & 4 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"mem": "node ../../dist/main --inspect"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-socket.io": "^10.4.7",
"@nestjs/platform-socket.io": "^10.4.8",
"@nestjs/schedule": "^4.1.1",
"@nestjs/swagger": "^8.0.5",
"@nestjs/typeorm": "^10.0.2",
"@nestjs/websockets": "^10.4.7",
"@nestjs/websockets": "^10.4.8",
"axios": "^1.7.7",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
Expand All @@ -39,13 +40,15 @@
"nest-winston": "^1.9.7",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"socket.io": "^4.8.1",
"typeorm": "^0.3.20",
"unzipper": "^0.12.3",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
"winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.0"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand All @@ -56,8 +59,10 @@
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/passport-google-oauth20": "^2.0.16",
"@types/passport-local": "^1.0.38",
"@types/supertest": "^6.0.0",
"@types/unzipper": "^0.10.10",
"@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"cz-emoji-conventional": "^1.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ChatModule } from '@/chat/chat.module';
import {
typeormDevelopConfig,
typeormProductConfig,
} from '@/configs/devTypeormConfig';
} from '@/configs/typeormConfig';
import { logger } from '@/configs/logger.config';
import { StockModule } from '@/stock/stock.module';
import { UserModule } from '@/user/user.module';
Expand Down
16 changes: 13 additions & 3 deletions packages/backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { GoogleAuthController } from '@/auth/google/googleAuth.controller';
import { GoogleAuthService } from '@/auth/google/googleAuth.service';
import { GoogleStrategy } from '@/auth/google/strategy/google.strategy';
import { SessionSerializer } from '@/auth/session/session.serializer';
import { TesterStrategy } from '@/auth/tester/strategy/tester.strategy';
import { TesterAuthController } from '@/auth/tester/testerAuth.controller';
import { TesterAuthService } from '@/auth/tester/testerAuth.service';
import { UserModule } from '@/user/user.module';

@Module({
imports: [UserModule],
controllers: [GoogleAuthController],
providers: [GoogleStrategy, GoogleAuthService, SessionSerializer],
imports: [UserModule, PassportModule.register({ session: true })],
controllers: [GoogleAuthController, TesterAuthController],
providers: [
GoogleStrategy,
GoogleAuthService,
SessionSerializer,
TesterAuthService,
TesterStrategy,
],
})
export class AuthModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('GoogleAuthService 테스트', () => {
};

test('oauthId와 type에 맞는 유저가 있으면 해당 객체를 반환한다.', async () => {
const user: User = {
const user: Partial<User> = {
id: 1,
role: Role.USER,
type: OauthType.GOOGLE,
Expand Down
5 changes: 2 additions & 3 deletions packages/backend/src/auth/google/strategy/google.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy, VerifyCallback } from 'passport-google-oauth20';
import { GoogleAuthService } from '@/auth/google/googleAuth.service';
import { OauthType } from '@/user/domain/ouathType';
import { Logger } from 'winston';

export interface OauthUserInfo {
type: OauthType;
Expand All @@ -15,7 +14,7 @@ export interface OauthUserInfo {

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {
constructor(private readonly googleAuthService: GoogleAuthService, @Inject('winston') private readonly logger: Logger) {
constructor(private readonly googleAuthService: GoogleAuthService) {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/auth/session.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { MemoryStore } from 'express-session';

export const MEMORY_STORE = 'memoryStore';
export const MEMORY_STORE = Symbol('memoryStore');

@Module({
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface SessionSocket extends Socket {
session?: User;
}

interface PassportSession extends SessionData {
export interface PassportSession extends SessionData {
passport: { user: User };
}

Expand Down
30 changes: 30 additions & 0 deletions packages/backend/src/auth/session/websocketSession.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MemoryStore } from 'express-session';
import { Socket } from 'socket.io';
import { websocketCookieParse } from '@/auth/session/cookieParser';
import { PassportSession } from '@/auth/session/webSocketSession.guard';

export class WebsocketSessionService {
constructor(private readonly sessionStore: MemoryStore) {}

async getAuthenticatedUser(socket: Socket) {
try {
const cookieValue = websocketCookieParse(socket);
const session = await this.getSession(cookieValue);
return session ? session.passport.user : null;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
return null;
}
}

private getSession(cookieValue: string) {
return new Promise<PassportSession | null>((resolve) => {
this.sessionStore.get(cookieValue, (err: Error, session) => {
if (err || !session) {
resolve(null);
}
resolve(session as PassportSession);
});
});
}
}
16 changes: 16 additions & 0 deletions packages/backend/src/auth/tester/guard/tester.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class TestAuthGuard extends AuthGuard('local') {
constructor() {
super();
}

async canActivate(context: ExecutionContext) {
const isActivate = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return isActivate;
}
}
16 changes: 16 additions & 0 deletions packages/backend/src/auth/tester/strategy/tester.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { TesterAuthService } from '@/auth/tester/testerAuth.service';

@Injectable()
export class TesterStrategy extends PassportStrategy(Strategy) {
constructor(private readonly testerAuthService: TesterAuthService) {
super();
}

async validate(username: string, password: string, done: CallableFunction) {
const user = await this.testerAuthService.attemptAuthentication();
done(null, user);
}
}
51 changes: 51 additions & 0 deletions packages/backend/src/auth/tester/testerAuth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
import {
ApiOkResponse,
ApiOperation,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import { Request, Response } from 'express';
import { TestAuthGuard } from '@/auth/tester/guard/tester.guard';

@ApiTags('Auth')
@Controller('auth/tester')
export class TesterAuthController {
constructor() {}

@ApiOperation({
summary: '테스터 로그인 api',
description: '테스터로 로그인합니다.',
})
@ApiQuery({
name: 'username',
required: true,
description: '테스터 아이디(값만 넣으면 됨)',
})
@ApiQuery({
name: 'password',
required: true,
description: '테스터 비밀번호(값만 넣으면 됨)',
})
@Get('/login')
@UseGuards(TestAuthGuard)
async handleLogin(@Res() response: Response) {
response.redirect('/');
}

@ApiOperation({
summary: '로그인 상태 확인',
description: '로그인 상태를 확인합니다.',
})
@ApiOkResponse({
description: '로그인된 상태',
example: { message: 'Authenticated' },
})
@Get('/status')
async user(@Req() request: Request) {
if (request.user) {
return { message: 'Authenticated' };
}
return { message: 'Not Authenticated' };
}
}
11 changes: 11 additions & 0 deletions packages/backend/src/auth/tester/testerAuth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { UserService } from '@/user/user.service';

@Injectable()
export class TesterAuthService {
constructor(private readonly userService: UserService) {}

async attemptAuthentication() {
return await this.userService.registerTester();
}
}
71 changes: 62 additions & 9 deletions packages/backend/src/chat/chat.controller.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
import { Controller, Get, Query } from '@nestjs/common';
import {
Body,
Controller,
Get,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiOkResponse,
ApiOperation,
} from '@nestjs/swagger';
import SessionGuard from '@/auth/session/session.guard';
import { ChatGateway } from '@/chat/chat.gateway';
import { ChatService } from '@/chat/chat.service';
import { ChatScrollRequest } from '@/chat/dto/chat.request';
import { ToggleLikeApi } from '@/chat/decorator/like.decorator';
import { ChatScrollQuery } from '@/chat/dto/chat.request';
import { ChatScrollResponse } from '@/chat/dto/chat.response';
import { LikeRequest } from '@/chat/dto/like.request';
import { LikeService } from '@/chat/like.service';
import { GetUser } from '@/common/decorator/user.decorator';
import { User } from '@/user/domain/user.entity';

@Controller('chat')
export class ChatController {
constructor(private readonly chatService: ChatService) {}
constructor(
private readonly chatService: ChatService,
private readonly likeService: LikeService,
private readonly chatGateWay: ChatGateway,
) {}

@ApiOperation({
summary: '채팅 스크롤 조회 API',
Expand All @@ -29,11 +48,45 @@ export class ChatController {
},
})
@Get()
async findChatList(@Query() request: ChatScrollRequest) {
return await this.chatService.scrollNextChat(
request.stockId,
request.latestChatId,
request.pageSize,
);
async findChatList(
@Query() request: ChatScrollQuery,
@Req() req: Express.Request,
) {
const user = req.user as User;
return await this.chatService.scrollChat(request, user?.id);
}

@UseGuards(SessionGuard)
@ToggleLikeApi()
@Post('like')
async toggleChatLike(@Body() request: LikeRequest, @GetUser() user: User) {
const result = await this.likeService.toggleLike(user.id, request.chatId);
this.chatGateWay.broadcastLike(result);
return result;
}

@ApiOperation({
summary: '채팅 스크롤 조회 API(좋아요 순)',
description: '좋아요 순으로 채팅을 스크롤하여 조회한다.',
})
@ApiOkResponse({
description: '스크롤 조회 성공',
type: ChatScrollResponse,
})
@ApiBadRequestResponse({
description: '스크롤 크기 100 초과',
example: {
message: 'pageSize should be less than 100',
error: 'Bad Request',
statusCode: 400,
},
})
@Get('/like')
async findChatListByLike(
@Query() request: ChatScrollQuery,
@Req() req: Express.Request,
) {
const user = req.user as User;
return await this.chatService.scrollChatByLike(request, user?.id);
}
}
Loading

0 comments on commit cacfefd

Please sign in to comment.