Skip to content

Commit

Permalink
Merge pull request #312 from boostcampwm2023/feature/socket-io
Browse files Browse the repository at this point in the history
[Feat] 채팅 기능 개발
  • Loading branch information
ccxz84 authored Dec 5, 2023
2 parents 4f62b0f + 58e878b commit 3e1966f
Show file tree
Hide file tree
Showing 60 changed files with 1,595 additions and 153 deletions.
71 changes: 34 additions & 37 deletions app/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,48 +1,45 @@
# Node.js 기본 이미지
FROM node:20.9.0-alpine as builder

# 작업 디렉토리 설정
FROM node:20.9.0-alpine AS base

FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
WORKDIR /app

# 루트의 package.json 파일 복사
COPY package*.json ./

# packages 폴더의 내용 복사
# packages 폴더가 여러 개 있을 경우, 필요에 따라 추가 복사
COPY packages ./packages

# # app/backend 폴더의 내용 복사
COPY app/backend ./app/backend

# 의존성 설치
RUN npm install --global turbo
COPY . .
RUN turbo prune --scope=backend --docker

# Add lockfile and package.json's of isolated subworkspace
FROM base AS installer
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app

# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/package-lock.json ./package-lock.json
RUN npm ci

# Build the project
COPY --from=builder /app/out/full/ .

# app/backend 폴더로 이동
# Generate Prisma Client Code
WORKDIR /app/app/backend

# 애플리케이션 빌드
RUN npx prisma generate
RUN npm run build

# .env 파일이 있으면 복사, 없으면 빈 파일 생성
RUN if [ ! -f ./.env ]; then \
touch ./.env; \
fi

# 실행을 위한 최종 스테이지
FROM node:20.9.0-alpine as runner

# 필요한 도구 설치
RUN apk add --no-cache netcat-openbsd

WORKDIR /app
RUN npx turbo run build --filter=backend...

FROM base AS runner
WORKDIR /app

# 빌더 스테이지에서 필요한 파일만 복사
COPY --from=builder /app/app/backend/dist /app/dist
COPY --from=builder /app/app/backend/prisma /app/prisma
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/packages /app/packages
COPY --from=builder /app/app/backend/.env /app/app/backend/.env
COPY --from=installer /app/app/backend/dist /app/dist
COPY --from=installer /app/app/backend/prisma /app/prisma
COPY --from=installer /app/node_modules /app/node_modules
COPY --from=installer /app/packages /app/packages
COPY app/backend/.env /app/app/backend/.env

# 필요한 경우, 런타임에 필요한 추가 파일 복사
# 예: 환경변수 파일, 정적 파일 등
Expand All @@ -57,4 +54,4 @@ RUN chmod +x /docker-entrypoint.sh
RUN sed -i 's/\r//' /wait-for-it.sh
RUN sed -i 's/\r//' /docker-entrypoint.sh

CMD ["/wait-for-it.sh", "db:3306", "--", "/docker-entrypoint.sh"]
CMD ["/wait-for-it.sh", "db:3306", "--", "/docker-entrypoint.sh"]
60 changes: 60 additions & 0 deletions app/backend/Dockerfile.legacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Node.js 기본 이미지
FROM node:20.9.0-alpine as builder

# 작업 디렉토리 설정
WORKDIR /app

# 루트의 package.json 파일 복사
COPY package*.json ./

# packages 폴더의 내용 복사
# packages 폴더가 여러 개 있을 경우, 필요에 따라 추가 복사
COPY packages ./packages

# # app/backend 폴더의 내용 복사
COPY app/backend ./app/backend

# 의존성 설치
RUN npm ci

# app/backend 폴더로 이동
WORKDIR /app/app/backend

# 애플리케이션 빌드
RUN npx prisma generate
RUN npm run build

# .env 파일이 있으면 복사, 없으면 빈 파일 생성
RUN if [ ! -f ./.env ]; then \
touch ./.env; \
fi

# 실행을 위한 최종 스테이지
FROM node:20.9.0-alpine as runner

# 필요한 도구 설치
RUN apk add --no-cache netcat-openbsd

WORKDIR /app

# 빌더 스테이지에서 필요한 파일만 복사
COPY --from=builder /app/app/backend/dist /app/dist
COPY --from=builder /app/app/backend/prisma /app/prisma
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/packages /app/packages
COPY --from=builder /app/app/backend/.env /app/app/backend/.env

# 필요한 경우, 런타임에 필요한 추가 파일 복사
# 예: 환경변수 파일, 정적 파일 등

# 애플리케이션 실행
COPY app/backend/wait-for-it.sh /wait-for-it.sh
COPY app/backend/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /wait-for-it.sh
RUN chmod +x /docker-entrypoint.sh

# 윈도우 개행문자 회피
RUN sed -i 's/\r//' /wait-for-it.sh
RUN sed -i 's/\r//' /docker-entrypoint.sh

CMD ["/wait-for-it.sh", "db:3306", "--", "/docker-entrypoint.sh"]
20 changes: 17 additions & 3 deletions app/backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ services:
environment:
- DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@db:3306/${DB_DATABASE_NAME}
- REDIS_HOST=redis
- MONGO_HOST=mongodb
- DOMAIN=${DOMAIN}
- PORT=${PORT}
depends_on:
- db
- redis
- mongodb
ports:
- "${PORT}:${PORT}"
- "${PORT}:${PORT}"
- "${SOCKET_PORT}:${SOCKET_PORT}"
db:
image: ccxz84/morak-mysql:latest
environment:
Expand All @@ -26,8 +29,19 @@ services:

redis:
image: redis:alpine
expose:
- "6379"
ports:
- "${REDIS_PORT}:6379"

mongodb:
image: ccxz84/morak-mongo:latest
ports:
- "27017:27017"
environment:
- CHAT_USER=${CHAT_USER}
- CHAT_PASSWORD=${CHAT_PASSWORD}
volumes:
- mongo-data:/data/db

volumes:
mysql-data:
mongo-data:
11 changes: 8 additions & 3 deletions app/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,30 @@
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/mapped-types": "*",
"@nestjs/mongoose": "^10.0.2",
"@nestjs/passport": "^10.0.2",
"@nestjs/platform-socket.io": "^10.2.10",
"@nestjs/swagger": "^7.1.14",
"@nestjs/websockets": "^10.2.10",
"@prisma/client": "^5.5.2",
"cache-manager-redis-store": "^2.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"dotenv": "^16.3.1",
"mongoose": "^8.0.2",
"passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"vault": "^1.0.0"
"@morak/vault": "^1.0.0"
},
"devDependencies": {
"@morak/apitype": "^1.0.0",
"@morak/eslint-config": "*",
"@morak/tsconfig": "*",
"@morak/chat": "^1.0.0",
"@morak/eslint-config": "^1.0.0",
"@morak/tsconfig": "^1.0.0",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
Expand Down
8 changes: 6 additions & 2 deletions app/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { PrismaModule } from 'prisma/prisma.module';
import { MemberModule } from './member/member.module';
import { MogacoModule } from './mogaco-boards/mogaco-boards.module';
import { GroupsModule } from './groups/groups.module';
import { ChatModule } from './chat/chat.module';
import * as redisStore from 'cache-manager-redis-store';
import { getSecret } from 'vault';
import { MongooseModule } from '@nestjs/mongoose';
import { getSecret } from '@morak/vault';

@Module({
imports: [
Expand All @@ -17,13 +19,15 @@ import { getSecret } from 'vault';
host: getSecret('REDIS_HOST'),
port: getSecret('REDIS_PORT'),
}),
MongooseModule.forRoot(`mongodb://${getSecret('CHAT_USER')}:${getSecret('CHAT_PASSWORD')}@${getSecret('MONGO_HOST')}:${getSecret('MONGO_PORT')}/${getSecret(`MONGO_CHAT_DB`)}`), // MongoDB 연결 문자열
PrismaModule,
AuthModule,
MemberModule,
MogacoModule,
GroupsModule,
ChatModule,
],
controllers: [AppController],
providers: [],
})
export class AppModule {}
export class AppModule {}
2 changes: 1 addition & 1 deletion app/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { AuthService } from './auth.service';
import { GoogleOauthGuard } from './guards/google-oauth.guard';
import { LogoutDto } from './dto/user.dto';
import { getSecret } from 'vault';
import { getSecret } from '@morak/vault';

@ApiTags('Oauth API')
@Controller('auth')
Expand Down
2 changes: 1 addition & 1 deletion app/backend/src/auth/auth.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { Member } from '@prisma/client';
import { CreateUserDto } from './dto/user.dto';
import { getSecret } from 'vault';
import { getSecret } from '@morak/vault';

@Injectable()
export class AuthRepository {
Expand Down
2 changes: 1 addition & 1 deletion app/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { JwtService } from '@nestjs/jwt';
import { AuthRepository } from './auth.repository';
import { CreateUserDto } from './dto/user.dto';
import { Payload, Tokens } from './interface';
import { getSecret } from 'vault';
import { getSecret } from '@morak/vault';

@Injectable()
export class AuthService {
Expand Down
2 changes: 1 addition & 1 deletion app/backend/src/auth/strategies/at.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Payload } from '../interface';
import { getSecret } from 'vault';
import { getSecret } from '@morak/vault';
import { Member } from '@prisma/client';
import { AuthRepository } from '../auth.repository';

Expand Down
2 changes: 1 addition & 1 deletion app/backend/src/auth/strategies/google.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback, Profile } from 'passport-google-oauth20';
import { getSecret } from 'vault';
import { getSecret } from '@morak/vault';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
Expand Down
2 changes: 1 addition & 1 deletion app/backend/src/auth/strategies/rt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Request } from 'express';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Payload } from '../interface';
import { getSecret } from 'vault';
import { getSecret } from '@morak/vault';

@Injectable()
export class RtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
Expand Down
68 changes: 68 additions & 0 deletions app/backend/src/chat/chat.gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { WebSocketGateway, WebSocketServer, SubscribeMessage, ConnectedSocket, MessageBody } from '@nestjs/websockets';
import { Server } from 'socket.io';
import { AuthUser } from '@morak/chat/dist/server/decorator/user.decorator';
import { JoinRoom, LeaveRoom } from '@morak/chat/dist/server/decorator/room.decorator';
import { ChatMessage } from '@morak/chat/dist/server/decorator/message.decorator';
import { RequestGetPrevChatMessage, StatusCode } from '@morak/chat/dist/interface/message.interface';
import { getSecret } from '@morak/vault';
import ChatService from './chat.service';
import { User } from '@morak/chat/src/interface/user.interface';
import { Socket } from 'dgram';
import { UseGuards } from '@nestjs/common';
import { ChatGuard } from './chat.guard';

const port = parseInt(getSecret('SOCKET_PORT'), 10);

@WebSocketGateway(port, {
namespace: 'chat',
cors: { origin: '*' },
transports: ['websocket'],
})
class ChatGateway {
@WebSocketServer() server: Server;
constructor(private readonly chatService: ChatService) {}

@UseGuards(ChatGuard)
@SubscribeMessage('joinRoom')
joinRoom(@ConnectedSocket() client: Socket, @AuthUser() user: User, @JoinRoom() room: string) {
// 231203 ccxz84 | chat logging 유저 룸 떠나기 메시지 로깅 필요
try {
// 231205 ccxz84 | chat error 유저 룸 조인 에러 체크를 위한 try catch
client.emit('postJoinRoom', StatusCode.success, 'join Room Success');
} catch (error) {
client.emit('postJoinRoom', StatusCode.error, 'join Room Error');
}
}

@SubscribeMessage('leaveRoom')
leaveRoom(@AuthUser() user: User, @LeaveRoom() room: string) {
// 231203 ccxz84 | chat logging 유저 룸 떠나기 메시지 로깅 필요
console.log(`${user} leave ${room}`);
}

@SubscribeMessage('chatMessage')
handleMessage(@AuthUser() user: User, @ChatMessage() message) {
this.chatService.writeMessageDB(message);
this.server.to(message.room).emit('chat', StatusCode.success, message);
}

@SubscribeMessage('requestPrevMessage')
getPrevMessages(@ConnectedSocket() client: Socket, @MessageBody() data: RequestGetPrevChatMessage) {
try {
if (data.room && data.cursorDate) {
const messages = this.chatService.loadMessageDB(data.room, data.cursorDate);
messages.then((data) => {
client.emit('receivePrevMessage', StatusCode.success, data);
});
}
} catch (error) {
client.emit('receivePrevMessage', StatusCode.error, 'Get Chat Message Error');
}
}

afterInit(server: Server) {
console.log('웹소켓 서버 초기화 ✅');
}
}

export default ChatGateway;
19 changes: 19 additions & 0 deletions app/backend/src/chat/chat.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import ChatService from "./chat.service";

@Injectable()
export class ChatGuard implements CanActivate {
constructor(private chatService: ChatService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const message = context.switchToWs().getData();

if (!message || !message.room || !message.user) {
return false;
}

const { user, room } = message;

return await this.chatService.isUserInGroup(room, user);
}
}
Loading

0 comments on commit 3e1966f

Please sign in to comment.