Skip to content

Commit

Permalink
feat(be): separate socket server (#250)
Browse files Browse the repository at this point in the history
* refactor: separate socket server

* chore: auto-fix linting and formatting issues

* fix: remove spec file

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
yu-yj215 and github-actions[bot] authored Dec 3, 2024
1 parent 2a11f78 commit 226207a
Show file tree
Hide file tree
Showing 17 changed files with 152 additions and 74 deletions.
11 changes: 4 additions & 7 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,12 @@ jobs:
# 서버 디렉토리로 이동하여 환경 변수 설정
cd apps/server
echo "DATABASE_URL=${{ secrets.DEV_DATABASE_URL }}" > .env
echo "JWT_ACCESS_SECRET=${{ secrets.JWT_ACCESS_SECRET }}" >> .env
echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> .env
echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" >> .env
echo "${{SERVER_ENV}}" > .env
# pm2로 기존 프로세스 종료 및 새 프로세스 시작
pm2 stop ask-it || true
pm2 delete ask-it || true
pm2 stop all || true
pm2 delete all || true
pnpx prisma generate
pnpx prisma migrate deploy
pnpm build
pm2 start pnpm --name "ask-it" -- run start:prod
pm2 start ecosystem.config.js
26 changes: 26 additions & 0 deletions apps/server/ecosystem.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
apps: [
{
name: 'http-server',
script: 'pnpm',
args: 'run start:prod',
interpreter: 'none',
cwd: './',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
},
{
name: 'ws-server',
script: 'pnpm',
args: 'run start:prod',
interpreter: 'none',
cwd: './',
env: {
NODE_ENV: 'production',
PORT: 4000,
},
},
],
};
21 changes: 21 additions & 0 deletions apps/server/src/common/request-socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BroadcastEventDto } from '@socket/dto/broadcast-event.dto';

const SOCKET_SERVER_HOST = process.env.SOCKET_SERVER_HOST;
const SOCKET_SERVER_PORT = process.env.SOCKET_SERVER_PORT;

export const requestSocket = async ({ event, content, sessionId, token }: BroadcastEventDto) => {
try {
await fetch(`${SOCKET_SERVER_HOST}:${SOCKET_SERVER_PORT}/api/socket/broadcast`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event,
content,
sessionId,
token,
}),
});
} catch (error) {
throw error;
}
};
13 changes: 10 additions & 3 deletions apps/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as cookieParser from 'cookie-parser';

import { AppModule } from './app.module';

async function bootstrap() {
async function bootstrap(port: number) {
const app = await NestFactory.create(AppModule);

app.use(cookieParser());
Expand All @@ -25,6 +25,13 @@ async function bootstrap() {

app.useWebSocketAdapter(new IoAdapter(app));

await app.listen(process.env.PORT ?? 3000);
await app.listen(port);
}

const args = process.argv.slice(2);

if (args.includes('http')) {
bootstrap(Number(process.env.API_SERVER_PORT ?? '3000'));
} else if (args.includes('ws')) {
bootstrap(Number(process.env.SOCKET_SERVER_PORT ?? '4000'));
}
bootstrap();
26 changes: 15 additions & 11 deletions apps/server/src/questions/questions.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ToggleQuestionLikeSwagger } from './swagger/toggle-question.swagger';
import { BaseDto } from '@common/base.dto';
import { SessionTokenValidationGuard } from '@common/guards/session-token-validation.guard';
import { TransformInterceptor } from '@common/interceptors/transform.interceptor';
import { requestSocket } from '@common/request-socket';
import {
UpdateQuestionBodyDto,
UpdateQuestionClosedDto,
Expand All @@ -38,16 +39,13 @@ import {
UpdateQuestionClosedSwagger,
UpdateQuestionPinnedSwagger,
} from '@questions/swagger/update-question.swagger';
import { SocketGateway } from '@socket/socket.gateway';
import { SOCKET_EVENTS } from '@socket/socket.constant';

@ApiTags('Questions')
@UseInterceptors(TransformInterceptor)
@Controller('questions')
export class QuestionsController {
constructor(
private readonly questionsService: QuestionsService,
private readonly socketGateway: SocketGateway,
) {}
constructor(private readonly questionsService: QuestionsService) {}

@Get()
@GetQuestionSwagger()
Expand All @@ -66,7 +64,8 @@ export class QuestionsController {
const { sessionId, token } = createQuestionDto;
const resultForOwner = { question: createdQuestion };
const resultForOther = { question: { ...createdQuestion, isOwner: false } };
this.socketGateway.broadcastNewQuestion(sessionId, token, resultForOther);
const event = SOCKET_EVENTS.QUESTION_CREATED;
await requestSocket({ sessionId, token, event, content: resultForOther });
return resultForOwner;
}

Expand All @@ -86,7 +85,8 @@ export class QuestionsController {
);
const { sessionId, token } = updateQuestionBodyDto;
const result = { question: updatedQuestion };
this.socketGateway.broadcastQuestionUpdate(sessionId, token, result);
const event = SOCKET_EVENTS.QUESTION_UPDATED;
await requestSocket({ sessionId, token, event, content: result });
return result;
}

Expand All @@ -97,7 +97,8 @@ export class QuestionsController {
const { sessionId, token } = data;
await this.questionsService.deleteQuestion(questionId, req.question, data);
const resultForOther = { questionId };
this.socketGateway.broadcastQuestionDelete(sessionId, token, resultForOther);
const event = SOCKET_EVENTS.QUESTION_DELETED;
await requestSocket({ sessionId, token, event, content: resultForOther });
return {};
}

Expand All @@ -112,7 +113,8 @@ export class QuestionsController {
const updatedQuestion = await this.questionsService.updateQuestionPinned(questionId, updateQuestionPinnedDto);
const { sessionId, token } = updateQuestionPinnedDto;
const result = { question: updatedQuestion };
this.socketGateway.broadcastQuestionUpdate(sessionId, token, result);
const event = SOCKET_EVENTS.QUESTION_UPDATED;
await requestSocket({ sessionId, token, event, content: result });
return result;
}

Expand All @@ -127,7 +129,8 @@ export class QuestionsController {
const updatedQuestion = await this.questionsService.updateQuestionClosed(questionId, updateQuestionClosedDto);
const { sessionId, token } = updateQuestionClosedDto;
const result = { question: updatedQuestion };
this.socketGateway.broadcastQuestionUpdate(sessionId, token, result);
const event = SOCKET_EVENTS.QUESTION_UPDATED;
await requestSocket({ sessionId, token, event, content: result });
return result;
}

Expand All @@ -143,7 +146,8 @@ export class QuestionsController {
const { sessionId, token } = toggleQuestionLikeDto;
const resultForOwner = { liked, likesCount };
const resultForOther = { questionId, liked: false, likesCount };
this.socketGateway.broadcastQuestionLike(sessionId, token, resultForOther);
const event = SOCKET_EVENTS.QUESTION_LIKED;
await requestSocket({ sessionId, token, event, content: resultForOther });
return resultForOwner;
}
}
3 changes: 1 addition & 2 deletions apps/server/src/questions/questions.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import { PrismaModule } from '@prisma-alias/prisma.module';
import { QuestionExistenceGuard } from '@questions/guards/question-existence.guard';
import { QuestionOwnershipGuard } from '@questions/guards/question-ownership.guard';
import { RepliesRepository } from '@replies/replies.repository';
import { SocketModule } from '@socket/socket.module';

@Module({
imports: [PrismaModule, SessionTokenModule, SocketModule],
imports: [PrismaModule, SessionTokenModule],
controllers: [QuestionsController],
providers: [QuestionsService, QuestionsRepository, QuestionExistenceGuard, QuestionOwnershipGuard, RepliesRepository],
exports: [QuestionExistenceGuard, QuestionsRepository],
Expand Down
17 changes: 8 additions & 9 deletions apps/server/src/replies/replies.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,16 @@ import { UpdateReplySwagger } from './swagger/update-reply.swagger';

import { SessionTokenValidationGuard } from '@common/guards/session-token-validation.guard';
import { TransformInterceptor } from '@common/interceptors/transform.interceptor';
import { requestSocket } from '@common/request-socket';
import { QuestionExistenceGuard } from '@questions/guards/question-existence.guard';
import { SocketGateway } from '@socket/socket.gateway';
import { SOCKET_EVENTS } from '@socket/socket.constant';
import { BaseDto } from '@src/common/base.dto';

@ApiTags('Replies')
@UseInterceptors(TransformInterceptor)
@Controller('replies')
export class RepliesController {
constructor(
private readonly repliesService: RepliesService,
private readonly socketGateway: SocketGateway,
) {}
constructor(private readonly repliesService: RepliesService) {}

@Post()
@CreateReplySwagger()
Expand All @@ -51,7 +49,7 @@ export class RepliesController {
const resultForOwner = { reply: { ...reply, isHost } };
const resultForOther = { reply: { ...reply, isHost, isOwner: false } };
const { sessionId, token } = createReplyDto;
this.socketGateway.broadcastNewReply(sessionId, token, resultForOther);
await requestSocket({ sessionId, token, event: SOCKET_EVENTS.REPLY_CREATED, content: resultForOther });
return resultForOwner;
}

Expand All @@ -63,7 +61,7 @@ export class RepliesController {
const updatedReply = await this.repliesService.updateBody(replyId, updateReplyBodyDto);
const { sessionId, token } = updateReplyBodyDto;
const result = { reply: updatedReply };
this.socketGateway.broadcastReplyUpdate(sessionId, token, result);
await requestSocket({ sessionId, token, event: SOCKET_EVENTS.REPLY_UPDATED, content: result });
return result;
}

Expand All @@ -74,7 +72,7 @@ export class RepliesController {
const { sessionId, token } = data;
const { questionId } = await this.repliesService.deleteReply(replyId, token, request['reply']);
const resultForOther = { replyId, questionId };
this.socketGateway.broadcastReplyDelete(sessionId, token, resultForOther);
await requestSocket({ sessionId, token, event: SOCKET_EVENTS.REPLY_DELETED, content: resultForOther });
return {};
}

Expand All @@ -87,7 +85,8 @@ export class RepliesController {
const { sessionId, token } = toggleReplyLikeDto;
const resultForOwner = { liked, likesCount };
const resultForOther = { replyId, liked: false, likesCount, questionId };
this.socketGateway.broadcastReplyLike(sessionId, token, resultForOther);
const event = SOCKET_EVENTS.REPLY_LIKED;
await requestSocket({ sessionId, token, event, content: resultForOther });
return resultForOwner;
}
}
3 changes: 1 addition & 2 deletions apps/server/src/replies/replies.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import { SessionTokenModule } from '@common/guards/session-token.module';
import { PrismaModule } from '@prisma-alias/prisma.module';
import { QuestionsModule } from '@questions/questions.module';
import { SessionsRepository } from '@sessions/sessions.repository';
import { SocketModule } from '@socket/socket.module';

@Module({
imports: [PrismaModule, SessionTokenModule, QuestionsModule, SocketModule],
imports: [PrismaModule, SessionTokenModule, QuestionsModule],
controllers: [RepliesController],
providers: [RepliesService, RepliesRepository, SessionsRepository],
})
Expand Down
11 changes: 5 additions & 6 deletions apps/server/src/sessions-auth/sessions-auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,15 @@ import { BaseDto } from '@common/base.dto';
import { SessionTokenValidationGuard } from '@common/guards/session-token-validation.guard';
import { JwtPayloadInterceptor } from '@common/interceptors/jwt-payload.interceptor';
import { TransformInterceptor } from '@common/interceptors/transform.interceptor';
import { SocketGateway } from '@socket/socket.gateway';
import { requestSocket } from '@common/request-socket';
import { SOCKET_EVENTS } from '@socket/socket.constant';

@ApiTags('sessions-auth')
@UseInterceptors(TransformInterceptor)
@UseInterceptors(JwtPayloadInterceptor)
@Controller('sessions-auth')
export class SessionsAuthController {
constructor(
private readonly sessionsAuthService: SessionsAuthService,
private readonly socketGateway: SocketGateway,
) {}
constructor(private readonly sessionsAuthService: SessionsAuthService) {}

@Get()
@AuthSessionsSwagger()
Expand All @@ -56,7 +54,8 @@ export class SessionsAuthController {
async authorizeHost(@Param('userId', ParseIntPipe) userId: number, @Body() data: UpdateHostDto) {
const { sessionId, token } = data;
const result = { user: await this.sessionsAuthService.authorizeHost(userId, data) };
this.socketGateway.broadcastHostChange(sessionId, token, result);
const event = SOCKET_EVENTS.HOST_CHANGED;
await requestSocket({ sessionId, token, event, content: result });
return result;
}
}
3 changes: 1 addition & 2 deletions apps/server/src/sessions-auth/sessions-auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import { SessionTokenValidationGuard } from '@common/guards/session-token-valida
import { SessionTokenModule } from '@common/guards/session-token.module';
import { PrismaModule } from '@prisma-alias/prisma.module';
import { SessionsRepository } from '@sessions/sessions.repository';
import { SocketModule } from '@socket/socket.module';

@Module({
imports: [JwtModule.register({}), PrismaModule, SessionTokenModule, SocketModule],
imports: [JwtModule.register({}), PrismaModule, SessionTokenModule],
controllers: [SessionsAuthController],
providers: [SessionsAuthService, SessionsAuthRepository, SessionTokenValidationGuard, SessionsRepository],
})
Expand Down
11 changes: 5 additions & 6 deletions apps/server/src/sessions/sessions.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ import { TerminateSessionSwagger } from './swagger/terminate-session.swagger';
import { JwtAuthGuard } from '@auth/jwt-auth.guard';
import { SessionTokenValidationGuard } from '@common/guards/session-token-validation.guard';
import { TransformInterceptor } from '@common/interceptors/transform.interceptor';
import { SocketGateway } from '@socket/socket.gateway';
import { requestSocket } from '@common/request-socket';
import { SOCKET_EVENTS } from '@socket/socket.constant';

@ApiTags('Sessions')
@UseInterceptors(TransformInterceptor)
@Controller('sessions')
export class SessionsController {
constructor(
private readonly sessionsService: SessionsService,
private readonly socketGateway: SocketGateway,
) {}
constructor(private readonly sessionsService: SessionsService) {}

@Post()
@UseGuards(JwtAuthGuard)
Expand All @@ -47,7 +45,8 @@ export class SessionsController {
@UseGuards(SessionTokenValidationGuard)
async terminateSession(@Param('sessionId') sessionId: string, @Body() { token }: TerminateSessionDto) {
const result = await this.sessionsService.terminateSession(sessionId, token);
this.socketGateway.broadcastSessionEnd(sessionId, token, result);
const event = SOCKET_EVENTS.SESSION_ENDED;
await requestSocket({ sessionId, token, event, content: result });
return result;
}
}
3 changes: 1 addition & 2 deletions apps/server/src/sessions/sessions.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import { AuthModule } from '@auth/auth.module';
import { SessionTokenModule } from '@common/guards/session-token.module';
import { PrismaModule } from '@prisma-alias/prisma.module';
import { SessionsAuthRepository } from '@sessions-auth/sessions-auth.repository';
import { SocketModule } from '@socket/socket.module';

@Module({
imports: [PrismaModule, JwtModule.register({}), AuthModule, SessionTokenModule, SocketModule],
imports: [PrismaModule, JwtModule.register({}), AuthModule, SessionTokenModule],
controllers: [SessionsController],
providers: [SessionsService, SessionsRepository, SessionsAuthRepository],
exports: [SessionsService],
Expand Down
12 changes: 12 additions & 0 deletions apps/server/src/socket/dto/broadcast-event.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IsNotEmpty } from 'class-validator';

import { BaseDto } from '@common/base.dto';
import { SOCKET_EVENTS } from '@socket/socket.constant';

export class BroadcastEventDto extends BaseDto {
@IsNotEmpty({ message: '데이터는 필수입니다.' })
content: Record<any, any>;

@IsNotEmpty({ message: '이벤트명은 필수입니다.' })
event: (typeof SOCKET_EVENTS)[keyof typeof SOCKET_EVENTS];
}
22 changes: 22 additions & 0 deletions apps/server/src/socket/socket.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const SOCKET_EVENTS = {
QUESTION_CREATED: 'questionCreated',
QUESTION_UPDATED: 'questionUpdated',
QUESTION_DELETED: 'questionDeleted',
QUESTION_LIKED: 'questionLiked',

REPLY_CREATED: 'replyCreated',
REPLY_UPDATED: 'replyUpdated',
REPLY_DELETED: 'replyDeleted',
REPLY_LIKED: 'replyLiked',

CREATE_CHAT: 'createChat',
CHAT_MESSAGE: 'chatMessage',
CHAT_ERROR: 'chatError',

INVALID_CONNECTION: 'invalidConnection',
DUPLICATED_CONNECTION: 'duplicatedConnection',
PARTICIPANT_COUNT_UPDATED: 'participantCountUpdated',

HOST_CHANGED: 'hostChanged',
SESSION_ENDED: 'sessionEnded',
} as const;
Loading

0 comments on commit 226207a

Please sign in to comment.