Skip to content

Commit a19dd73

Browse files
committed
file: 지워지는 데이터 기준으로 파일 위치 이동
1 parent e3c9ef4 commit a19dd73

3 files changed

Lines changed: 190 additions & 189 deletions

File tree

Firebase/functions/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ import {
3333
} from "./fcm/schedule";
3434

3535
import {
36-
removeTodoNotificationDocuments,
37-
removeCompletedTodoNotificationRecords,
38-
cleanupSoftDeletedTodos,
39-
cleanupUnusedTodoNotificationRecords
36+
cleanupSoftDeletedTodos
4037
} from "./todo/cleanup";
4138

4239
import {
@@ -61,6 +58,9 @@ import {
6158
} from "./notification/deletion";
6259

6360
import {
61+
removeTodoNotificationDocuments,
62+
removeCompletedTodoNotificationRecords,
63+
cleanupUnusedTodoNotificationRecords,
6464
cleanupSoftDeletedNotifications
6565
} from "./notification/cleanup";
6666

Firebase/functions/src/notification/cleanup.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,77 @@
1+
import { onDocumentDeleted, onDocumentUpdated } from "firebase-functions/v2/firestore";
12
import { onSchedule } from "firebase-functions/v2/scheduler";
23
import * as admin from "firebase-admin";
34
import * as logger from "firebase-functions/logger";
5+
import { toDate } from "../common/date";
46
import { toError } from "../common/error";
7+
import { FirestorePath } from "../common/firestorePath";
58

69
const LOCATION = "asia-northeast3";
710
const CLEANUP_BATCH_SIZE = 200;
11+
const DELETE_BATCH_SIZE = 200;
12+
const QUERY_BATCH_SIZE = 100;
13+
14+
// Todo 삭제 시 연결된 알림 문서와 발송 기록 문서의 동시 제거
15+
export const removeTodoNotificationDocuments = onDocumentDeleted({
16+
maxInstances: 1,
17+
document: "users/{userId}/todoLists/{todoId}",
18+
region: LOCATION
19+
},
20+
async (event) => {
21+
const userId = event.params.userId;
22+
const todoId = event.params.todoId;
23+
24+
try {
25+
await deleteByTodoId(userId, "notificationDispatches", todoId);
26+
await deleteByTodoId(userId, "notifications", todoId);
27+
} catch (error) {
28+
logger.error(
29+
"todo 삭제 후 notification 문서 정리 실패",
30+
toError(error),
31+
{
32+
userId,
33+
todoId,
34+
collections: ["notificationDispatches", "notifications"]
35+
}
36+
);
37+
}
38+
}
39+
);
40+
41+
// 지난 마감일 Todo 완료 시 재발송 방지 기록 정리
42+
export const removeCompletedTodoNotificationRecords = onDocumentUpdated({
43+
maxInstances: 1,
44+
document: "users/{userId}/todoLists/{todoId}",
45+
region: LOCATION
46+
},
47+
async (event) => {
48+
const beforeData = event.data?.before.data();
49+
const afterData = event.data?.after.data();
50+
const userId = event.params.userId;
51+
const todoId = event.params.todoId;
52+
53+
if (!beforeData || !afterData) { return; }
54+
if (beforeData.isCompleted === true || afterData.isCompleted !== true) { return; }
55+
56+
const dueDate = toDate(afterData.dueDate);
57+
58+
if (!dueDate || Date.now() <= dueDate.getTime()) { return; }
59+
60+
try {
61+
await deleteByTodoId(userId, "notificationDispatches", todoId);
62+
} catch (error) {
63+
logger.error(
64+
"완료된 todo의 notification record 정리 실패",
65+
toError(error),
66+
{
67+
userId,
68+
todoId,
69+
collection: "notificationDispatches"
70+
}
71+
);
72+
}
73+
}
74+
);
875

976
export const cleanupSoftDeletedNotifications = onSchedule({
1077
maxInstances: 1,
@@ -44,3 +111,122 @@ export const cleanupSoftDeletedNotifications = onSchedule({
44111
}
45112
}
46113
);
114+
115+
// 사용되지 않는 알림 발송 기록의 주기적 정리
116+
export const cleanupUnusedTodoNotificationRecords = onSchedule({
117+
maxInstances: 1,
118+
region: LOCATION,
119+
schedule: "0 * * * *",
120+
timeZone: "UTC"
121+
},
122+
async () => {
123+
try {
124+
await cleanupDispatchesByTodoQuery((lastDocument) => {
125+
let query = admin.firestore()
126+
.collectionGroup("todoLists")
127+
.where("isCompleted", "==", true)
128+
.where("dueDate", "<", admin.firestore.Timestamp.now())
129+
.orderBy("dueDate")
130+
.limit(QUERY_BATCH_SIZE);
131+
132+
if (lastDocument) {
133+
query = query.startAfter(lastDocument);
134+
}
135+
136+
return query;
137+
});
138+
} catch (error) {
139+
logger.error(
140+
"지난 마감일의 완료된 todo notification record 정리 실패",
141+
toError(error),
142+
{
143+
collectionGroup: "todoLists",
144+
filter: "isCompleted == true && dueDate < now",
145+
orderBy: "dueDate",
146+
queryBatchSize: QUERY_BATCH_SIZE
147+
}
148+
);
149+
}
150+
151+
try {
152+
await cleanupDispatchesByTodoQuery((lastDocument) => {
153+
let query = admin.firestore()
154+
.collectionGroup("todoLists")
155+
.where("dueDate", "==", null)
156+
.orderBy(admin.firestore.FieldPath.documentId())
157+
.limit(QUERY_BATCH_SIZE);
158+
159+
if (lastDocument) {
160+
query = query.startAfter(lastDocument);
161+
}
162+
163+
return query;
164+
});
165+
} catch (error) {
166+
logger.error(
167+
"마감일이 없는 todo notification record 정리 실패",
168+
toError(error),
169+
{
170+
collectionGroup: "todoLists",
171+
filter: "dueDate == null",
172+
orderBy: "__name__",
173+
queryBatchSize: QUERY_BATCH_SIZE
174+
}
175+
);
176+
}
177+
}
178+
);
179+
180+
// Todo 조회 쿼리를 순회하며 연결된 알림 발송 기록을 정리
181+
async function cleanupDispatchesByTodoQuery(
182+
makeQuery: (
183+
lastDocument?:
184+
FirebaseFirestore.QueryDocumentSnapshot<FirebaseFirestore.DocumentData>
185+
) => FirebaseFirestore.Query<FirebaseFirestore.DocumentData>
186+
): Promise<void> {
187+
let lastDocument:
188+
FirebaseFirestore.QueryDocumentSnapshot<FirebaseFirestore.DocumentData> | undefined;
189+
190+
while (true) {
191+
const snapshot = await makeQuery(lastDocument).get();
192+
if (snapshot.empty) { return; }
193+
194+
for (const todoDoc of snapshot.docs) {
195+
const userId = todoDoc.ref.parent.parent?.id;
196+
if (!userId) { continue; }
197+
198+
await deleteByTodoId(userId, "notificationDispatches", todoDoc.id);
199+
}
200+
201+
if (snapshot.size < QUERY_BATCH_SIZE) { return; }
202+
lastDocument = snapshot.docs[snapshot.docs.length - 1];
203+
}
204+
}
205+
206+
// 특정 Todo 연결 문서의 배치 단위 전체 삭제
207+
async function deleteByTodoId(
208+
userId: string,
209+
collectionName: "notificationDispatches" | "notifications",
210+
todoId: string
211+
): Promise<void> {
212+
while (true) {
213+
const collectionPath = collectionName === "notificationDispatches" ?
214+
FirestorePath.notificationDispatches(userId) :
215+
FirestorePath.notifications(userId);
216+
const snapshot = await admin.firestore()
217+
.collection(collectionPath)
218+
.where("todoId", "==", todoId)
219+
.limit(DELETE_BATCH_SIZE)
220+
.get();
221+
222+
if (snapshot.empty) { return; }
223+
224+
const batch = admin.firestore().batch();
225+
snapshot.docs.forEach((document) => {
226+
batch.delete(document.ref);
227+
});
228+
await batch.commit();
229+
230+
if (snapshot.size < DELETE_BATCH_SIZE) { return; }
231+
}
232+
}

0 commit comments

Comments
 (0)