Skip to content

Commit

Permalink
Merge pull request #360 from boostcampwm-2024/refactor-be-#357
Browse files Browse the repository at this point in the history
백엔드 로직 리팩토링
  • Loading branch information
github-actions[bot] authored Dec 2, 2024
2 parents 3322052 + 3649a90 commit 829adc3
Show file tree
Hide file tree
Showing 18 changed files with 304 additions and 233 deletions.
10 changes: 2 additions & 8 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ import { RedLockModule } from './red-lock/red-lock.module';
@Module({
imports: [
ScheduleModule.forRoot(),
ServeStaticModule.forRoot({
rootPath: path.join(__dirname, '..', '..', 'frontend', 'dist'),
}),
ConfigModule.forRoot({
isGlobal: true,
envFilePath: path.join(__dirname, '..', '.env'), // * nest 디렉터리 기준
Expand All @@ -38,18 +35,15 @@ import { RedLockModule } from './red-lock/red-lock.module';
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({

// type: 'sqlite',
// database: 'db.sqlite',
type: 'postgres',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'),
entities: [Node, Page, Edge, User, Workspace, Role],
logging: true,
synchronize: true,
logging: process.env.NODE_ENV === 'development',
synchronize: process.env.NODE_ENV === 'development',
}),
}),
NodeModule,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/auth/strategies/kakao.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class KakaoStrategy extends PassportStrategy(Strategy, 'kakao') {
provider: 'kakao',
email: profile._json.kakao_account.email,
};

let user = await this.authService.findUser(createUserDto);
if (!user) {
user = await this.authService.signUp(createUserDto);
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/auth/strategies/naver.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class NaverStrategy extends PassportStrategy(Strategy, 'naver') {
provider: 'naver',
email: profile._json.email,
};

let user = await this.authService.findUser(createUserDto);
if (!user) {
user = await this.authService.signUp(createUserDto);
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/auth/token/token.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class TokenService {
generateInviteToken(workspaceId: number, role: string): string {
// 초대용 JWT 토큰 생성
const payload = { workspaceId, role };

return this.jwtService.sign(payload, {
expiresIn: DAY, // 초대 유효 기간: 1일
secret: process.env.JWT_SECRET,
Expand Down Expand Up @@ -98,6 +99,7 @@ export class TokenService {
secure: true,
sameSite: 'strict',
});

response.clearCookie('refreshToken', {
httpOnly: true,
secure: true,
Expand Down
3 changes: 3 additions & 0 deletions apps/backend/src/edge/edge.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class EdgeController {
@HttpCode(HttpStatus.CREATED)
async createEdge(@Body() body: CreateEdgeDto) {
await this.edgeService.createEdge(body);

return {
message: EdgeResponseMessage.EDGE_CREATED,
};
Expand All @@ -44,6 +45,7 @@ export class EdgeController {
@Param('id', ParseIntPipe) id: number,
): Promise<{ message: string }> {
await this.edgeService.deleteEdge(id);

return {
message: EdgeResponseMessage.EDGE_DELETED,
};
Expand All @@ -59,6 +61,7 @@ export class EdgeController {
@Param('workspaceId') workspaceId: string, // Snowflake ID
): Promise<FindEdgesResponseDto> {
const edges = await this.edgeService.findEdgesByWorkspace(workspaceId);

return {
message: EdgeResponseMessage.EDGES_RETURNED,
edges,
Expand Down
7 changes: 7 additions & 0 deletions apps/backend/src/node/node.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class NodeController {
@HttpCode(HttpStatus.OK)
async getNodeById(@Param('id', ParseIntPipe) id: number) {
const node = await this.nodeService.findNodeById(id);

return {
message: NodeResponseMessage.NODE_RETURNED,
node: node,
Expand All @@ -59,6 +60,7 @@ export class NodeController {
@HttpCode(HttpStatus.CREATED)
async createNode(@Body() body: CreateNodeDto) {
await this.nodeService.createNode(body);

return {
message: NodeResponseMessage.NODE_CREATED,
};
Expand All @@ -76,6 +78,7 @@ export class NodeController {
@Param('id', ParseIntPipe) id: number,
): Promise<{ message: string }> {
await this.nodeService.deleteNode(id);

return {
message: NodeResponseMessage.NODE_DELETED,
};
Expand All @@ -92,6 +95,7 @@ export class NodeController {
@Body() body: UpdateNodeDto,
): Promise<{ message: string }> {
await this.nodeService.updateNode(id, body);

return {
message: NodeResponseMessage.NODE_UPDATED,
};
Expand All @@ -105,6 +109,7 @@ export class NodeController {
@HttpCode(HttpStatus.OK)
async getCoordinates(@Param('id', ParseIntPipe) id: number) {
const coordinate = await this.nodeService.getCoordinates(id);

return {
message: NodeResponseMessage.NODE_GET_COORDINAE,
coordinate: coordinate,
Expand All @@ -118,6 +123,7 @@ export class NodeController {
@Body() body: MoveNodeDto,
) {
await this.nodeService.moveNode(id, body);

return {
message: NodeResponseMessage.NODE_MOVED,
};
Expand All @@ -133,6 +139,7 @@ export class NodeController {
@Param('workspaceId') workspaceId: string, // Snowflake ID
): Promise<FindNodesResponseDto> {
const nodes = await this.nodeService.findNodesByWorkspace(workspaceId);

return {
message: NodeResponseMessage.NODES_RETURNED,
nodes,
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/node/node.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class NodeService {
async createLinkedNode(x: number, y: number, pageId: number): Promise<Node> {
// 페이지를 조회한다.
const existingPage = await this.pageRepository.findOneBy({ id: pageId });

// 노드를 생성한다.
const node = this.nodeRepository.create({ x, y });

Expand Down Expand Up @@ -71,6 +72,7 @@ export class NodeService {
if (!node) {
throw new NodeNotFoundException();
}

// 노드와 연결된 페이지를 조회한다.
const linkedPage = await this.pageRepository.findOneBy({
id: node.page.id,
Expand Down
4 changes: 4 additions & 0 deletions apps/backend/src/page/page.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class PageController {
@Body() body: CreatePageDto,
): Promise<CreatePageResponseDto> {
const newPage = await this.pageService.createPage(body);

return {
message: PageResponseMessage.PAGE_CREATED,
pageId: newPage.id,
Expand All @@ -63,6 +64,7 @@ export class PageController {
@Param('id', ParseIntPipe) id: number,
): Promise<MessageResponseDto> {
await this.pageService.deletePage(id);

return {
message: PageResponseMessage.PAGE_DELETED,
};
Expand All @@ -79,6 +81,7 @@ export class PageController {
@Body() body: UpdatePageDto,
): Promise<MessageResponseDto> {
await this.pageService.updatePage(id, body);

return {
message: PageResponseMessage.PAGE_UPDATED,
};
Expand All @@ -94,6 +97,7 @@ export class PageController {
@Param('workspaceId') workspaceId: string, // Snowflake ID
): Promise<FindPagesResponseDto> {
const pages = await this.pageService.findPagesByWorkspace(workspaceId);

return {
message: PageResponseMessage.PAGES_RETURNED,
pages,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/page/page.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { WorkspaceNotFoundException } from '../exception/workspace.exception';
import Redlock from 'redlock';

const RED_LOCK_TOKEN = 'RED_LOCK';

@Injectable()
export class PageService {
constructor(
Expand Down
3 changes: 1 addition & 2 deletions apps/backend/src/redis/redis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ type RedisPage = {
title?: string;
content?: string;
};

@Injectable()
export class RedisService {
// private readonly redisClient: Redis;

constructor(
@Inject(REDIS_CLIENT_TOKEN) private readonly redisClient: Redis,
@Inject(RED_LOCK_TOKEN) private readonly redisLock: Redlock,
Expand Down
72 changes: 39 additions & 33 deletions apps/backend/src/tasks/tasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RedisService } from '../redis/redis.service';
import { DataSource } from 'typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Page } from 'src/page/page.entity';

@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
Expand All @@ -18,7 +19,7 @@ export class TasksService {
// 시작 시간
const startTime = performance.now();
const keys = await this.redisService.getAllKeys('page:*');
console.log(keys);

Promise.allSettled(
keys.map(async (key: string) => {
const redisData = await this.redisService.get(key);
Expand All @@ -33,44 +34,15 @@ export class TasksService {

if (title) updateData.title = title;
if (content) updateData.content = JSON.parse(content);
const pageId = parseInt(key.split(':')[1]);
// title과 content 중 적어도 하나가 있을 때 업데이트 실행
if (Object.keys(updateData).length > 0) {
// 트랜잭션 시작
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.startTransaction();

// 갱신 시작
const pageRepository = queryRunner.manager.getRepository(Page);
await pageRepository.update(pageId, updateData);

// redis에서 데이터 삭제
await this.redisService.delete(key);

// 트랜잭션 커밋
await queryRunner.commitTransaction();
} catch (err) {
// 실패하면 postgres는 roll back하고 redis의 값을 살린다.
this.logger.error(err.stack);
await queryRunner.rollbackTransaction();
title && (await this.redisService.setField(key, 'title', title));
content &&
(await this.redisService.setField(key, 'content', content));

// Promise.all에서 실패를 인식하기 위해 에러를 던진다.
throw err;
} finally {
// 리소스 정리
await queryRunner.release();
}
}
// 업데이트 대상이 없다면 리턴
if (Object.keys(updateData).length === 0) return;
this.migrate(key, updateData);
}),
)
.then((results) => {
const endTime = performance.now();
this.logger.log(`총 개수 : ${results.length}개`);
console.log(results);
this.logger.log(
`성공 개수 : ${results.filter((result) => result.status === 'fulfilled').length}개`,
);
Expand All @@ -83,4 +55,38 @@ export class TasksService {
this.logger.error(err);
});
}

async migrate(key: string, updateData:Partial<{ title: string; content: any }>) {
const pageId = parseInt(key.split(':')[1]);

// 트랜잭션 시작
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.startTransaction();

// 갱신 시작
const pageRepository = queryRunner.manager.getRepository(Page);

// TODO : 페이지가 없으면 affect : 0을 반환하는데 이 부분 처리도 하는 게 좋을 듯...?
await pageRepository.update(pageId, updateData);

// redis에서 데이터 삭제
await this.redisService.delete(key);

// 트랜잭션 커밋
await queryRunner.commitTransaction();
} catch (err) {
// 실패하면 postgres는 roll back하고 redis의 값을 살린다.
this.logger.error(err.stack);
await queryRunner.rollbackTransaction();
updateData.title && (await this.redisService.setField(key, 'title', updateData.title));
updateData.content && (await this.redisService.setField(key, 'content', updateData.content));

// Promise.all에서 실패를 인식하기 위해 에러를 던진다.
throw err;
} finally {
// 리소스 정리
await queryRunner.release();
}
}
}
1 change: 1 addition & 0 deletions apps/backend/src/upload/upload.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class UploadController {
@UploadedFile() file: Express.Multer.File,
): Promise<ImageUploadResponseDto> {
const result = await this.uploadService.uploadImageToCloud(file);

return {
message: UploadResponseMessage.UPLOAD_IMAGE_SUCCESS,
url: result,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/upload/upload.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class UploadService {
});

await this.s3Client.send(command);

return `${this.configService.get('CLOUD_ENDPOINT')}/${this.configService.get('CLOUD_BUCKET_NAME')}/${key}`;
}
}
4 changes: 4 additions & 0 deletions apps/backend/src/workspace/workspace.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class WorkspaceController {
userId,
createWorkspaceDto,
);

return {
message: WorkspaceResponseMessage.WORKSPACE_CREATED,
workspaceId: newWorkspace.snowflakeId,
Expand Down Expand Up @@ -92,6 +93,7 @@ export class WorkspaceController {
async getUserWorkspaces(@Request() req) {
const userId = req.user.sub; // 인증된 사용자의 ID
const workspaces = await this.workspaceService.getUserWorkspaces(userId);

return {
message: WorkspaceResponseMessage.WORKSPACES_RETURNED,
workspaces,
Expand Down Expand Up @@ -173,6 +175,7 @@ export class WorkspaceController {
async makeWorkspacePublic(@Request() req, @Param('id') id: string) {
const userId = req.user.sub; // 인증된 사용자 ID
await this.workspaceService.updateVisibility(userId, id, 'public');

return { message: WorkspaceResponseMessage.WORKSPACE_UPDATED_TO_PUBLIC };
}

Expand All @@ -188,6 +191,7 @@ export class WorkspaceController {
async makeWorkspacePrivate(@Request() req, @Param('id') id: string) {
const userId = req.user.sub; // 인증된 사용자 ID
await this.workspaceService.updateVisibility(userId, id, 'private');

return { message: WorkspaceResponseMessage.WORKSPACE_UPDATED_TO_PRIVATE };
}
}
2 changes: 2 additions & 0 deletions apps/backend/src/workspace/workspace.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import { RoleModule } from '../role/role.module';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { TokenModule } from '../auth/token/token.module';
import { TokenService } from '../auth/token/token.service';
import { ConfigModule } from '@nestjs/config';

@Module({
imports: [
TypeOrmModule.forFeature([Workspace]),
UserModule,
RoleModule,
TokenModule,
ConfigModule,
],
controllers: [WorkspaceController],
providers: [
Expand Down
Loading

0 comments on commit 829adc3

Please sign in to comment.