Skip to content

Commit 720891c

Browse files
djk01281yewonJin
andcommitted
merge commit
Co-authored-by: 진예원 <[email protected]>
2 parents ac294b1 + 58fb5c3 commit 720891c

File tree

134 files changed

+1747
-1207
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+1747
-1207
lines changed

apps/backend/src/app.module.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import { Node } from './node/node.entity';
1212
import { User } from './user/user.entity';
1313
import { Workspace } from './workspace/workspace.entity';
1414
import { Role } from './role/role.entity';
15-
import { YjsModule } from './yjs/yjs.module';
1615
import * as path from 'path';
17-
import { ServeStaticModule } from '@nestjs/serve-static';
1816
import { UploadModule } from './upload/upload.module';
1917
import { AuthModule } from './auth/auth.module';
2018
import { UserModule } from './user/user.module';
@@ -49,7 +47,6 @@ import { RedLockModule } from './red-lock/red-lock.module';
4947
NodeModule,
5048
PageModule,
5149
EdgeModule,
52-
YjsModule,
5350
UploadModule,
5451
AuthModule,
5552
UserModule,

apps/backend/src/node/node.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export class NodeController {
135135
@ApiOperation({ summary: '특정 워크스페이스의 모든 노드들을 가져옵니다.' })
136136
@Get('/workspace/:workspaceId')
137137
@HttpCode(HttpStatus.OK)
138-
async findPagesByWorkspace(
138+
async findNodesByWorkspace(
139139
@Param('workspaceId') workspaceId: string, // Snowflake ID
140140
): Promise<FindNodesResponseDto> {
141141
const nodes = await this.nodeService.findNodesByWorkspace(workspaceId);

apps/backend/src/redis/redis.service.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,21 @@ import Redlock from 'redlock';
55
const REDIS_CLIENT_TOKEN = 'REDIS_CLIENT';
66
const RED_LOCK_TOKEN = 'RED_LOCK';
77

8-
type RedisPage = {
8+
export type RedisPage = {
99
title?: string;
1010
content?: string;
11+
emoji?: string;
12+
};
13+
14+
export type RedisNode = {
15+
x: number;
16+
y: number;
17+
};
18+
19+
export type RedisEdge = {
20+
fromNode: number;
21+
toNode: number;
22+
type: 'add' | 'delete';
1123
};
1224

1325
@Injectable()
@@ -29,7 +41,7 @@ export class RedisService {
2941
const data = await this.redisClient.hgetall(key);
3042
return Object.fromEntries(
3143
Object.entries(data).map(([field, value]) => [field, value]),
32-
) as RedisPage;
44+
);
3345
}
3446

3547
async set(key: string, value: object) {

apps/backend/src/tasks/tasks.service.ts

Lines changed: 156 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { Injectable, Logger } from '@nestjs/common';
22
import { Cron, CronExpression } from '@nestjs/schedule';
3-
import { RedisService } from '../redis/redis.service';
3+
import {
4+
RedisEdge,
5+
RedisPage,
6+
RedisNode,
7+
RedisService,
8+
} from '../redis/redis.service';
49
import { DataSource } from 'typeorm';
510
import { InjectDataSource } from '@nestjs/typeorm';
6-
import { Page } from 'src/page/page.entity';
11+
import { Page } from '../page/page.entity';
12+
import { Node } from '../node/node.entity';
13+
import { Edge } from '../edge/edge.entity';
714

815
@Injectable()
916
export class TasksService {
@@ -18,28 +25,15 @@ export class TasksService {
1825
this.logger.log('스케줄러 시작');
1926
// 시작 시간
2027
const startTime = performance.now();
21-
const keys = await this.redisService.getAllKeys('page:*');
28+
const pageKeys = await this.redisService.getAllKeys('page:*');
29+
const nodeKeys = await this.redisService.getAllKeys('node:*');
30+
const edgeKeys = await this.redisService.getAllKeys('edge:*');
2231

23-
Promise.allSettled(
24-
keys.map(async (key: string) => {
25-
const redisData = await this.redisService.get(key);
26-
// 데이터 없으면 오류
27-
if (!redisData) {
28-
throw new Error(`redis에 ${key}에 해당하는 데이터가 없습니다.`);
29-
}
30-
31-
const { title, content } = redisData;
32-
33-
const updateData: Partial<{ title: string; content: any }> = {};
34-
35-
if (title) updateData.title = title;
36-
if (content) updateData.content = JSON.parse(content);
37-
38-
// 업데이트 대상이 없다면 리턴
39-
if (Object.keys(updateData).length === 0) return;
40-
this.migrate(key, updateData);
41-
}),
42-
)
32+
Promise.allSettled([
33+
...pageKeys.map(this.migratePage.bind(this)),
34+
...nodeKeys.map(this.migrateNode.bind(this)),
35+
...edgeKeys.map(this.migrateEdge.bind(this)),
36+
])
4337
.then((results) => {
4438
const endTime = performance.now();
4539
this.logger.log(`총 개수 : ${results.length}개`);
@@ -56,7 +50,23 @@ export class TasksService {
5650
});
5751
}
5852

59-
async migrate(key: string, updateData:Partial<{ title: string; content: any }>) {
53+
async migratePage(key: string) {
54+
const redisData = (await this.redisService.get(key)) as RedisPage;
55+
// 데이터 없으면 오류
56+
if (!redisData) {
57+
throw new Error(`redis에 ${key}에 해당하는 데이터가 없습니다.`);
58+
}
59+
60+
const { title, content, emoji } = redisData;
61+
62+
const updateData: Partial<Page> = {};
63+
64+
if (title) updateData.title = title;
65+
if (content) updateData.content = JSON.parse(content);
66+
if (emoji) updateData.emoji = emoji;
67+
68+
// 업데이트 대상이 없다면 리턴
69+
if (Object.keys(updateData).length === 0) return;
6070
const pageId = parseInt(key.split(':')[1]);
6171

6272
// 트랜잭션 시작
@@ -79,8 +89,128 @@ export class TasksService {
7989
// 실패하면 postgres는 roll back하고 redis의 값을 살린다.
8090
this.logger.error(err.stack);
8191
await queryRunner.rollbackTransaction();
82-
updateData.title && (await this.redisService.setField(key, 'title', updateData.title));
83-
updateData.content && (await this.redisService.setField(key, 'content', updateData.content));
92+
updateData.title &&
93+
(await this.redisService.setField(key, 'title', updateData.title));
94+
updateData.content &&
95+
(await this.redisService.setField(key, 'content', JSON.parse(content)));
96+
updateData.emoji &&
97+
(await this.redisService.setField(key, 'emoji', updateData.emoji));
98+
99+
// Promise.all에서 실패를 인식하기 위해 에러를 던진다.
100+
throw err;
101+
} finally {
102+
// 리소스 정리
103+
await queryRunner.release();
104+
}
105+
}
106+
107+
async migrateNode(key: string) {
108+
const redisData = (await this.redisService.get(
109+
key,
110+
)) as unknown as RedisNode;
111+
// 데이터 없으면 오류
112+
if (!redisData) {
113+
throw new Error(`redis에 ${key}에 해당하는 데이터가 없습니다.`);
114+
}
115+
116+
const updateData: Partial<Node> = {
117+
x: Number(redisData.x),
118+
y: Number(redisData.y),
119+
};
120+
121+
// 쿼리 대상이 없다면 리턴
122+
if (Object.keys(updateData).length === 0) return;
123+
const nodeId = parseInt(key.split(':')[1]);
124+
125+
// 트랜잭션 시작
126+
const queryRunner = this.dataSource.createQueryRunner();
127+
try {
128+
await queryRunner.startTransaction();
129+
130+
// 갱신 시작
131+
const nodeRepository = queryRunner.manager.getRepository(Node);
132+
133+
// TODO : 페이지가 없으면 affect : 0을 반환하는데 이 부분 처리도 하는 게 좋을 듯...?
134+
await nodeRepository.update(nodeId, updateData);
135+
136+
// redis에서 데이터 삭제
137+
await this.redisService.delete(key);
138+
139+
// 트랜잭션 커밋
140+
await queryRunner.commitTransaction();
141+
} catch (err) {
142+
// 실패하면 postgres는 roll back하고 redis의 값을 살린다.
143+
this.logger.error(err.stack);
144+
await queryRunner.rollbackTransaction();
145+
await this.redisService.setField(key, 'x', updateData.x.toString());
146+
await this.redisService.setField(key, 'y', updateData.y.toString());
147+
148+
// Promise.all에서 실패를 인식하기 위해 에러를 던진다.
149+
throw err;
150+
} finally {
151+
// 리소스 정리
152+
await queryRunner.release();
153+
}
154+
}
155+
156+
async migrateEdge(key: string) {
157+
const redisData = (await this.redisService.get(
158+
key,
159+
)) as unknown as RedisEdge;
160+
// 데이터 없으면 오류
161+
if (!redisData) {
162+
throw new Error(`redis에 ${key}에 해당하는 데이터가 없습니다.`);
163+
}
164+
165+
// 트랜잭션 시작
166+
const queryRunner = this.dataSource.createQueryRunner();
167+
try {
168+
await queryRunner.startTransaction();
169+
170+
// 갱신 시작
171+
const edgeRepository = queryRunner.manager.getRepository(Edge);
172+
const nodeRepository = queryRunner.manager.getRepository(Node);
173+
174+
const fromNode = await nodeRepository.findOne({
175+
where: { id: redisData.fromNode },
176+
});
177+
178+
const toNode = await nodeRepository.findOne({
179+
where: { id: redisData.toNode },
180+
});
181+
182+
if (redisData.type === 'add') {
183+
await edgeRepository.save({ fromNode, toNode });
184+
}
185+
186+
if (redisData.type === 'delete') {
187+
const edge = await edgeRepository.findOne({
188+
where: { fromNode, toNode },
189+
});
190+
191+
await edgeRepository.delete({ id: edge.id });
192+
}
193+
194+
// redis에서 데이터 삭제
195+
await this.redisService.delete(key);
196+
197+
// 트랜잭션 커밋
198+
await queryRunner.commitTransaction();
199+
} catch (err) {
200+
// 실패하면 postgres는 roll back하고 redis의 값을 살린다.
201+
this.logger.error(err.stack);
202+
await queryRunner.rollbackTransaction();
203+
await this.redisService.setField(
204+
key,
205+
'fromNode',
206+
redisData.fromNode.toString(),
207+
);
208+
await this.redisService.setField(
209+
key,
210+
'toNode',
211+
redisData.toNode.toString(),
212+
);
213+
await this.redisService.setField(key, 'type', redisData.type);
84214

85215
// Promise.all에서 실패를 인식하기 위해 에러를 던진다.
86216
throw err;

apps/backend/src/yjs/yjs.module.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)