@@ -9,10 +9,9 @@ import { FirestorePath } from "../common/firestorePath";
99const LOCATION = "asia-northeast3" ;
1010const DELETE_DELAY_SECONDS = 5 ;
1111
12- type NotificationDeletionTaskData = {
12+ type NotificationDeletionPayload = {
1313 userId : string ;
1414 notificationId : string ;
15- createdAt ?: FirebaseFirestore . Timestamp | Date | null ;
1615} ;
1716
1817export const requestPushNotificationDeletion = onCall ( {
@@ -37,45 +36,27 @@ export const requestPushNotificationDeletion = onCall({
3736 const notificationRef = admin . firestore ( ) . doc ( FirestorePath . notification ( userId , notificationId ) ) ;
3837 const notificationSnapshot = await notificationRef . get ( ) ;
3938
40- if ( ! notificationSnapshot . exists ) {
39+ if ( ! notificationSnapshot . exists || notificationSnapshot . data ( ) ?. isDeleted === true ) {
4140 throw new HttpsError ( "not-found" , "Notification을 찾을 수 없습니다." ) ;
4241 }
4342
44- const taskRef = admin . firestore ( ) . collection ( "notificationDeletionTasks" ) . doc ( ) ;
45- const taskData = {
46- userId,
47- notificationId,
48- createdAt : admin . firestore . FieldValue . serverTimestamp ( )
49- } ;
50-
5143 try {
52- await taskRef . set ( taskData ) ;
5344 await notificationRef . set ( {
5445 // deletingAt: 삭제 요청은 되었지만, 5초 유예 후 최종 삭제되기 전 상태를 의미한다.
55- deletingAt : admin . firestore . FieldValue . serverTimestamp ( )
46+ deletingAt : admin . firestore . FieldValue . serverTimestamp ( ) ,
47+ isDeleted : false
5648 } , { merge : true } ) ;
5749
5850 const queue = getFunctions ( ) . taskQueue (
5951 `locations/${ LOCATION } /functions/completePushNotificationDeletion`
6052 ) ;
6153 await queue . enqueue (
62- { taskId : taskRef . id } ,
54+ { userId , notificationId } ,
6355 { scheduleDelaySeconds : DELETE_DELAY_SECONDS }
6456 ) ;
6557 } catch ( error ) {
66- try {
67- await taskRef . delete ( ) ;
68- } catch ( cleanupError ) {
69- logger . warn ( "notificationDeletionTasks 정리 실패" , {
70- userId,
71- notificationId,
72- taskId : taskRef . id ,
73- error : normalizeError ( cleanupError )
74- } ) ;
75- }
76-
7758 const currentNotificationSnapshot = await notificationRef . get ( ) ;
78- if ( currentNotificationSnapshot . exists ) {
59+ if ( currentNotificationSnapshot . exists && currentNotificationSnapshot . data ( ) ?. isDeleted !== true ) {
7960 await notificationRef . update ( {
8061 deletingAt : admin . firestore . FieldValue . delete ( )
8162 } ) ;
@@ -111,28 +92,15 @@ export const undoPushNotificationDeletion = onCall({
11192 if ( ! notificationId ) {
11293 throw new HttpsError ( "invalid-argument" , "notificationId가 필요합니다." ) ;
11394 }
114-
115- const taskSnapshot = await admin . firestore ( )
116- . collection ( "notificationDeletionTasks" )
117- . where ( "userId" , "==" , userId )
118- . where ( "notificationId" , "==" , notificationId )
119- . get ( ) ;
12095 const notificationRef = admin . firestore ( ) . doc ( FirestorePath . notification ( userId , notificationId ) ) ;
12196
12297 try {
12398 const notificationSnapshot = await notificationRef . get ( ) ;
124- if ( notificationSnapshot . exists ) {
99+ if ( notificationSnapshot . exists && notificationSnapshot . data ( ) ?. isDeleted !== true ) {
125100 await notificationRef . update ( {
126- deletingAt : admin . firestore . FieldValue . delete ( )
127- } ) ;
128- }
129-
130- if ( ! taskSnapshot . empty ) {
131- const batch = admin . firestore ( ) . batch ( ) ;
132- taskSnapshot . docs . forEach ( ( document ) => {
133- batch . delete ( document . ref ) ;
101+ deletingAt : admin . firestore . FieldValue . delete ( ) ,
102+ isDeleted : false
134103 } ) ;
135- await batch . commit ( ) ;
136104 }
137105 } catch ( error ) {
138106 logger . error ( "푸시 알림 삭제 취소 실패" , {
@@ -154,45 +122,54 @@ export const completePushNotificationDeletion = onTaskDispatched({
154122 rateLimits : { maxDispatchesPerSecond : 200 } ,
155123 } ,
156124 async ( request ) => {
157- const taskId = typeof request . data ?. taskId === "string" ? request . data . taskId . trim ( ) : "" ;
158- if ( ! taskId ) {
125+ const payload = parseDeletionPayload ( request . data ) ;
126+ if ( ! payload ) {
159127 logger . warn ( "유효하지 않은 푸시 알림 삭제 payload" , request . data ) ;
160128 return ;
161129 }
162130
163- const taskRef = admin . firestore ( ) . collection ( "notificationDeletionTasks" ) . doc ( taskId ) ;
164- const taskSnapshot = await taskRef . get ( ) ;
165- if ( ! taskSnapshot . exists ) { return ; }
166-
167- const taskData = taskSnapshot . data ( ) as NotificationDeletionTaskData | undefined ;
168- const userId = typeof taskData ?. userId === "string" ? taskData . userId : "" ;
169- const notificationId = typeof taskData ?. notificationId === "string" ? taskData . notificationId : "" ;
170- if ( ! userId || ! notificationId ) {
171- logger . warn ( "notificationDeletionTasks 문서 형식이 올바르지 않습니다." , { taskId} ) ;
172- return ;
173- }
131+ const { userId, notificationId } = payload ;
174132
175133 const notificationRef = admin . firestore ( ) . doc ( FirestorePath . notification ( userId , notificationId ) ) ;
176134
177135 try {
178136 const notificationSnapshot = await notificationRef . get ( ) ;
179137 const deletingAt = notificationSnapshot . data ( ) ?. deletingAt ;
138+ const isDeleted = notificationSnapshot . data ( ) ?. isDeleted === true ;
180139
181- if ( ! notificationSnapshot . exists || ! deletingAt ) {
182- await taskRef . delete ( ) ;
140+ if ( ! notificationSnapshot . exists || ! deletingAt || isDeleted ) {
183141 return ;
184142 }
185143
186- await notificationRef . delete ( ) ;
187- await taskRef . delete ( ) ;
144+ await notificationRef . set ( {
145+ deletingAt : admin . firestore . FieldValue . delete ( ) ,
146+ isDeleted : true
147+ } , { merge : true } ) ;
188148 } catch ( error ) {
189149 logger . error ( "푸시 알림 최종 삭제 실패" , {
190150 userId,
191151 notificationId,
192- taskId,
193152 error : normalizeError ( error )
194153 } ) ;
195154 throw error ;
196155 }
197156 }
198157) ;
158+
159+ function parseDeletionPayload ( data : unknown ) : NotificationDeletionPayload | null {
160+ const userId = typeof ( data as NotificationDeletionPayload | undefined ) ?. userId === "string" ?
161+ ( data as NotificationDeletionPayload ) . userId . trim ( ) :
162+ "" ;
163+ const notificationId = typeof ( data as NotificationDeletionPayload | undefined ) ?. notificationId === "string" ?
164+ ( data as NotificationDeletionPayload ) . notificationId . trim ( ) :
165+ "" ;
166+
167+ if ( ! userId || ! notificationId ) {
168+ return null ;
169+ }
170+
171+ return {
172+ userId,
173+ notificationId
174+ } ;
175+ }
0 commit comments