Skip to content

Commit af8912d

Browse files
committed
refactor: 테스트 코드를 바탕으로 Todo의 soft deletion와 동일한 형태로 undo 로직 개선
1 parent ce64236 commit af8912d

5 files changed

Lines changed: 37 additions & 195 deletions

File tree

DevLog/Infra/Service/PushNotificationService.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,7 @@ final class PushNotificationService {
193193
}
194194

195195
guard let snapshot else { return }
196-
let unreadPushCount = snapshot.documents.filter { document in
197-
!(document.data()[PushNotificationFieldKey.deletingAt.rawValue] is Timestamp)
198-
}.count
196+
let unreadPushCount = snapshot.documents.count
199197
subject.send(unreadPushCount)
200198
}
201199

@@ -304,8 +302,7 @@ private extension PushNotificationService {
304302

305303
func makeResponse(from snapshot: QueryDocumentSnapshot) -> PushNotificationResponse? {
306304
let data = snapshot.data()
307-
if data[PushNotificationFieldKey.deletingAt.rawValue] is Timestamp ||
308-
(data[PushNotificationFieldKey.isDeleted.rawValue] as? Bool) == true {
305+
if (data[PushNotificationFieldKey.isDeleted.rawValue] as? Bool) == true {
309306
return nil
310307
}
311308
guard
@@ -336,7 +333,6 @@ private extension PushNotificationService {
336333
case isRead
337334
case todoId
338335
case todoCategory
339-
case deletingAt // 삭제 요청으로 앱의 로컬 데이터에서 deletion이 된 상태
340336
case isDeleted // 삭제 요청으로 서버에서 soft deletion이 된 상태
341337
}
342338
}

DevLog/Infra/Service/WebPageService.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,6 @@ final class WebPageService {
125125
private extension WebPageService {
126126
func makeResponse(from snapshot: QueryDocumentSnapshot) -> WebPageResponse? {
127127
let data = snapshot.data()
128-
if data[WebPageFieldKey.deletingAt.rawValue] is Timestamp {
129-
return nil
130-
}
131128
guard
132129
(data[WebPageFieldKey.isDeleted.rawValue] as? Bool) != true,
133130
let title = data[WebPageFieldKey.title.rawValue] as? String,
@@ -151,7 +148,6 @@ private extension WebPageService {
151148
case url
152149
case displayURL
153150
case imageURL
154-
case deletingAt // 삭제 요청으로 앱의 로컬 데이터에서 deletion이 된 상태
155151
case isDeleted // 삭제 요청으로 서버에서 soft deletion이 된 상태
156152
}
157153
}

Firebase/functions/src/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ import {
5252

5353
import {
5454
requestPushNotificationDeletion,
55-
undoPushNotificationDeletion,
56-
completePushNotificationDeletion
55+
undoPushNotificationDeletion
5756
} from "./notification/deletion";
5857

5958
import {
@@ -65,8 +64,7 @@ import {
6564

6665
import {
6766
requestWebPageDeletion,
68-
undoWebPageDeletion,
69-
completeWebPageDeletion
67+
undoWebPageDeletion
7068
} from "./webPage/deletion";
7169

7270
import {
@@ -121,10 +119,8 @@ export {
121119
undoTodoDeletion,
122120
requestPushNotificationDeletion,
123121
undoPushNotificationDeletion,
124-
completePushNotificationDeletion,
125122
cleanupSoftDeletedNotifications,
126123
requestWebPageDeletion,
127124
undoWebPageDeletion,
128-
completeWebPageDeletion,
129125
cleanupSoftDeletedWebPages
130126
};
Lines changed: 16 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import { onCall, HttpsError } from "firebase-functions/v2/https";
2-
import { onTaskDispatched } from "firebase-functions/v2/tasks";
3-
import { getFunctions } from "firebase-admin/functions";
2+
import { FieldValue } from "firebase-admin/firestore";
43
import * as admin from "firebase-admin";
54
import * as logger from "firebase-functions/logger";
65
import { toError } from "../common/error";
76
import { FirestorePath } from "../common/firestorePath";
87

98
const LOCATION = "asia-northeast3";
10-
const DELETE_DELAY_SECONDS = 5;
11-
12-
type NotificationDeletionPayload = {
13-
userId: string;
14-
notificationId: string;
15-
};
169

1710
export const requestPushNotificationDeletion = onCall({
1811
cors: true,
@@ -42,30 +35,23 @@ export const requestPushNotificationDeletion = onCall({
4235

4336
try {
4437
await notificationRef.set({
45-
// deletingAt: 삭제 요청은 되었지만, 5초 유예 후 최종 삭제되기 전 상태를 의미한다.
46-
deletingAt: admin.firestore.FieldValue.serverTimestamp(),
47-
isDeleted: false
38+
deletingAt: FieldValue.delete(),
39+
isDeleted: true
4840
}, {merge: true});
49-
50-
const queue = getFunctions().taskQueue(
51-
`locations/${LOCATION}/functions/completePushNotificationDeletion`
52-
);
53-
await queue.enqueue(
54-
{ userId, notificationId },
55-
{scheduleDelaySeconds: DELETE_DELAY_SECONDS}
56-
);
5741
} catch (error) {
58-
const currentNotificationSnapshot = await notificationRef.get();
59-
if (currentNotificationSnapshot.exists && currentNotificationSnapshot.data()?.isDeleted !== true) {
60-
await notificationRef.update({
61-
deletingAt: admin.firestore.FieldValue.delete()
62-
});
42+
try {
43+
const currentNotificationSnapshot = await notificationRef.get();
44+
if (currentNotificationSnapshot.exists && currentNotificationSnapshot.data()?.isDeleted === true) {
45+
await notificationRef.update({
46+
deletingAt: FieldValue.delete(),
47+
isDeleted: false
48+
});
49+
}
50+
} catch (cleanupError) {
51+
logger.error("푸시 알림 삭제 요청 cleanup 실패", toError(cleanupError), { userId, notificationId });
6352
}
6453

65-
logger.error("푸시 알림 삭제 요청 실패", toError(error), {
66-
userId,
67-
notificationId
68-
});
54+
logger.error("푸시 알림 삭제 요청 실패", toError(error), { userId, notificationId });
6955
throw new HttpsError("internal", "푸시 알림 삭제 요청에 실패했습니다.");
7056
}
7157

@@ -95,9 +81,9 @@ export const undoPushNotificationDeletion = onCall({
9581

9682
try {
9783
const notificationSnapshot = await notificationRef.get();
98-
if (notificationSnapshot.exists && notificationSnapshot.data()?.isDeleted !== true) {
84+
if (notificationSnapshot.exists && notificationSnapshot.data()?.isDeleted === true) {
9985
await notificationRef.update({
100-
deletingAt: admin.firestore.FieldValue.delete(),
86+
deletingAt: FieldValue.delete(),
10187
isDeleted: false
10288
});
10389
}
@@ -112,61 +98,3 @@ export const undoPushNotificationDeletion = onCall({
11298
return {success: true};
11399
}
114100
);
115-
116-
export const completePushNotificationDeletion = onTaskDispatched({
117-
maxInstances: 5,
118-
region: LOCATION,
119-
retryConfig: { maxAttempts: 3, minBackoffSeconds: 5},
120-
rateLimits: { maxDispatchesPerSecond: 5 },
121-
},
122-
async (request) => {
123-
const payload = parseDeletionPayload(request.data);
124-
if (!payload) {
125-
logger.warn("유효하지 않은 푸시 알림 삭제 payload", request.data);
126-
return;
127-
}
128-
129-
const { userId, notificationId } = payload;
130-
131-
const notificationRef = admin.firestore().doc(FirestorePath.notification(userId, notificationId));
132-
133-
try {
134-
const notificationSnapshot = await notificationRef.get();
135-
const deletingAt = notificationSnapshot.data()?.deletingAt;
136-
const isDeleted = notificationSnapshot.data()?.isDeleted === true;
137-
138-
if (!notificationSnapshot.exists || !deletingAt || isDeleted) {
139-
return;
140-
}
141-
142-
await notificationRef.set({
143-
deletingAt: admin.firestore.FieldValue.delete(),
144-
isDeleted: true
145-
}, { merge: true });
146-
} catch (error) {
147-
logger.error("푸시 알림 최종 삭제 실패", toError(error), {
148-
userId,
149-
notificationId
150-
});
151-
throw error;
152-
}
153-
}
154-
);
155-
156-
function parseDeletionPayload(data: unknown): NotificationDeletionPayload | null {
157-
const userId = typeof (data as NotificationDeletionPayload | undefined)?.userId === "string" ?
158-
(data as NotificationDeletionPayload).userId.trim() :
159-
"";
160-
const notificationId = typeof (data as NotificationDeletionPayload | undefined)?.notificationId === "string" ?
161-
(data as NotificationDeletionPayload).notificationId.trim() :
162-
"";
163-
164-
if (!userId || !notificationId) {
165-
return null;
166-
}
167-
168-
return {
169-
userId,
170-
notificationId
171-
};
172-
}
Lines changed: 17 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import { onCall, HttpsError } from "firebase-functions/v2/https";
2-
import { onTaskDispatched } from "firebase-functions/v2/tasks";
3-
import { getFunctions } from "firebase-admin/functions";
2+
import { FieldValue } from "firebase-admin/firestore";
43
import * as admin from "firebase-admin";
54
import * as logger from "firebase-functions/logger";
65
import { toError } from "../common/error";
76
import { FirestorePath } from "../common/firestorePath";
87

98
const LOCATION = "asia-northeast3";
10-
const DELETE_DELAY_SECONDS = 5;
11-
12-
type WebPageDeletionPayload = {
13-
userId: string;
14-
urlString: string;
15-
};
169

1710
export const requestWebPageDeletion = onCall({
1811
cors: true,
@@ -47,23 +40,22 @@ export const requestWebPageDeletion = onCall({
4740

4841
try {
4942
await webPageRef.set({
50-
// deletingAt: 삭제 요청은 되었지만, 5초 유예 후 최종 soft delete 되기 전 상태를 의미한다.
51-
deletingAt: admin.firestore.FieldValue.serverTimestamp(),
52-
isDeleted: false
43+
deletingAt: FieldValue.delete(),
44+
isDeleted: true
5345
}, { merge: true });
54-
55-
const queue = getFunctions().taskQueue(
56-
`locations/${LOCATION}/functions/completeWebPageDeletion`
57-
);
58-
await queue.enqueue(
59-
{ userId, urlString },
60-
{ scheduleDelaySeconds: DELETE_DELAY_SECONDS }
61-
);
6246
} catch (error) {
63-
const currentWebPageSnapshot = await webPageRef.get();
64-
if (currentWebPageSnapshot.exists && currentWebPageSnapshot.data()?.isDeleted !== true) {
65-
await webPageRef.update({
66-
deletingAt: admin.firestore.FieldValue.delete()
47+
try {
48+
const currentWebPageSnapshot = await webPageRef.get();
49+
if (currentWebPageSnapshot.exists && currentWebPageSnapshot.data()?.isDeleted === true) {
50+
await webPageRef.update({
51+
deletingAt: FieldValue.delete(),
52+
isDeleted: false
53+
});
54+
}
55+
} catch (cleanupError) {
56+
logger.error("웹페이지 삭제 요청 cleanup 실패", toError(cleanupError), {
57+
userId,
58+
urlString
6759
});
6860
}
6961

@@ -110,9 +102,9 @@ export const undoWebPageDeletion = onCall({
110102

111103
try {
112104
const currentWebPageSnapshot = await webPageRef.get();
113-
if (currentWebPageSnapshot.exists && currentWebPageSnapshot.data()?.isDeleted !== true) {
105+
if (currentWebPageSnapshot.exists && currentWebPageSnapshot.data()?.isDeleted === true) {
114106
await webPageRef.update({
115-
deletingAt: admin.firestore.FieldValue.delete(),
107+
deletingAt: FieldValue.delete(),
116108
isDeleted: false
117109
});
118110
}
@@ -127,69 +119,3 @@ export const undoWebPageDeletion = onCall({
127119
return { success: true };
128120
}
129121
);
130-
131-
export const completeWebPageDeletion = onTaskDispatched({
132-
maxInstances: 5,
133-
region: LOCATION,
134-
retryConfig: { maxAttempts: 3, minBackoffSeconds: 5 },
135-
rateLimits: { maxDispatchesPerSecond: 5 },
136-
},
137-
async (request) => {
138-
const payload = parseDeletionPayload(request.data);
139-
if (!payload) {
140-
logger.warn("유효하지 않은 웹페이지 삭제 payload", request.data);
141-
return;
142-
}
143-
144-
const { userId, urlString } = payload;
145-
const webPageSnapshot = await admin.firestore()
146-
.collection(FirestorePath.webPages(userId))
147-
.where("url", "==", urlString)
148-
.limit(1)
149-
.get();
150-
if (webPageSnapshot.empty) {
151-
return;
152-
}
153-
154-
const webPageRef = webPageSnapshot.docs[0].ref;
155-
156-
try {
157-
const currentWebPageSnapshot = await webPageRef.get();
158-
const deletingAt = currentWebPageSnapshot.data()?.deletingAt;
159-
const isDeleted = currentWebPageSnapshot.data()?.isDeleted === true;
160-
161-
if (!currentWebPageSnapshot.exists || !deletingAt || isDeleted) {
162-
return;
163-
}
164-
165-
await webPageRef.set({
166-
deletingAt: admin.firestore.FieldValue.delete(),
167-
isDeleted: true
168-
}, { merge: true });
169-
} catch (error) {
170-
logger.error("웹페이지 최종 soft delete 실패", toError(error), {
171-
userId,
172-
urlString
173-
});
174-
throw error;
175-
}
176-
}
177-
);
178-
179-
function parseDeletionPayload(data: unknown): WebPageDeletionPayload | null {
180-
const userId = typeof (data as WebPageDeletionPayload | undefined)?.userId === "string" ?
181-
(data as WebPageDeletionPayload).userId.trim() :
182-
"";
183-
const urlString = typeof (data as WebPageDeletionPayload | undefined)?.urlString === "string" ?
184-
(data as WebPageDeletionPayload).urlString.trim() :
185-
"";
186-
187-
if (!userId || !urlString) {
188-
return null;
189-
}
190-
191-
return {
192-
userId,
193-
urlString
194-
};
195-
}

0 commit comments

Comments
 (0)