From 8b49326a3ad49b86c309cb3f6c9ffb04133d44b4 Mon Sep 17 00:00:00 2001 From: MurakawaTakuya Date: Tue, 28 Jan 2025 01:11:21 +0900 Subject: [PATCH] =?UTF-8?q?=E7=9B=AE=E6=A8=99=E3=82=84=E6=8A=95=E7=A8=BF?= =?UTF-8?q?=E3=81=AE=E4=BD=9C=E6=88=90=E3=83=BB=E5=89=8A=E9=99=A4=E3=83=BB?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=81=AB=E5=BF=9C=E3=81=98=E3=81=A6Cloud=20t?= =?UTF-8?q?asks=E3=81=AB=E9=80=9A=E7=9F=A5=E3=82=92=E4=BD=9C=E6=88=90?= =?UTF-8?q?=E3=83=BB=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/src/index.ts | 2 +- functions/src/tasks.ts | 190 ++++++++++++++++++++++++++++++----------- 2 files changed, 141 insertions(+), 51 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index 5b786b0..0b81e2a 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -115,7 +115,7 @@ export const firestore = onRequest({ region: region }, async (req, res) => { export { createTasksOnGoalCreate, deleteTasksOnGoalDelete, - deleteTasksOnPostCreate, + updateTasksOnPostUpdate, } from "./tasks"; // テスト用API diff --git a/functions/src/tasks.ts b/functions/src/tasks.ts index 33d8c20..507945b 100644 --- a/functions/src/tasks.ts +++ b/functions/src/tasks.ts @@ -11,6 +11,60 @@ const tasksClient = new CloudTasksClient(); const projectId = process.env.GCP_PROJECT_ID; const region = "asia-northeast1"; const queue = "deadline-notification-queue"; +const marginTime = 10; + +// queuePath, postData, etaを受け取り、タスクを作成 +const createTask = async ( + queuePath: string, + postData: { goalId: string; marginTime: number }, + eta: Date +) => { + const accessToken = process.env.NOTIFICATION_KEY; + const goalId = postData.goalId; + + const now = new Date(); + // タスクの名前をgoalIdと作成日時を結合したものに設定 + const name = `${queuePath}/tasks/${goalId}-${now.getTime()}`; + logger.info(`Creating task: ${name}`); + await tasksClient.createTask({ + parent: queuePath, + task: { + name, + httpRequest: { + httpMethod: "POST", + url: "https://firestore-okdtj725ta-an.a.run.app/notification", + headers: { + "Content-Type": "application/json", + token: `${accessToken}`, + }, + body: Buffer.from(JSON.stringify(postData)).toString("base64"), + }, + scheduleTime: { seconds: Math.floor(eta.getTime() / 1000) }, + }, + }); + logger.info(`Task created for ${goalId} with ${name}`); +}; + +// queuePath, goalIdで始まるタスクを削除 +const deleteTask = async (queuePath: string, goalId: string) => { + const taskPrefix = `${queuePath}/tasks/${goalId}`; + logger.info(`Deleting tasks for ${goalId} with prefix ${taskPrefix}`); + + const [tasks] = await tasksClient.listTasks({ parent: queuePath }); + const tasksToDelete = tasks.filter( + (task) => task.name && task.name.startsWith(taskPrefix) + ); + + for (const task of tasksToDelete) { + if (task.name) { + logger.info("Deleting task:", task.name); + await tasksClient.deleteTask({ name: task.name }); + logger.info("Task deleted:", task.name); + } + } + + logger.info("All tasks with prefix deleted for goalId:", goalId); +}; export const createTasksOnGoalCreate = onDocumentCreated( { region: region, document: "goal/{goalId}" }, @@ -32,45 +86,31 @@ export const createTasksOnGoalCreate = onDocumentCreated( try { const goalData = event.data.data(); - const marginTime = 10; + const postData = { + goalId: event.params.goalId, + marginTime, + }; // 期限のmarginTime分前にタスクを設定 const deadline = new Date( goalData.deadline.toDate().getTime() - marginTime * 60 * 1000 ); - const goalId = event.params.goalId; - const postData = { - goalId, - marginTime, - }; - const queuePath = tasksClient.queuePath(projectId, region, queue); - const accessToken = process.env.NOTIFICATION_KEY; - - await tasksClient.createTask({ - parent: queuePath, - task: { - name: `${queuePath}/tasks/${goalId}`, // タスクの名前をgoalIdに設定 - httpRequest: { - httpMethod: "POST", - url: "https://firestore-okdtj725ta-an.a.run.app/notification", - headers: { - "Content-Type": "application/json", - token: `${accessToken}`, - }, - body: Buffer.from(JSON.stringify(postData)).toString("base64"), - }, - scheduleTime: { seconds: Math.floor(deadline.getTime() / 1000) }, - }, - }); - logger.info("Task created for goalId:", goalId); + createTask( + tasksClient.queuePath(projectId, region, queue), + postData, + deadline + ); } catch (error) { logger.error("Error scheduling task:", error); } } ); -// 目標を削除した時にtasksから通知予定を削除する -export const deleteTasksOnGoalDelete = onDocumentDeleted( - { region: region, document: "goal/{goalId}" }, +// 目標を完了した時にtasksから通知予定を削除する +export const updateTasksOnPostUpdate = onDocumentUpdated( + { + region: region, + document: "goal/{goalId}", + }, async (event) => { if (process.env.NODE_ENV !== "production") { return; @@ -86,24 +126,80 @@ export const deleteTasksOnGoalDelete = onDocumentDeleted( return; } - try { - const goalId = event.params.goalId; - const queuePath = tasksClient.queuePath(projectId, region, queue); - const taskName = `${queuePath}/tasks/${goalId}`; // goalIdが名前になっているタスクを削除 - await tasksClient.deleteTask({ name: taskName }); - logger.info("Task deleted for goalId:", goalId); - } catch (error) { - logger.error("Error deleting task:", error); + if ( + event.data.before.data().post === null && + event.data.after.data().post !== null + ) { + // 目標を完了(完了投稿を作成)した場合 + // null -> post に変更された場合はタスクを削除 + try { + const goalId = event.params.goalId; + const queuePath = tasksClient.queuePath(projectId, region, queue); + await deleteTask(queuePath, goalId); + } catch (error) { + logger.error("Error deleting task:", error); + } + } else if ( + event.data.before.data().post !== null && + event.data.after.data().post === null + ) { + // 完了投稿を削除した場合 + // post -> null に変更された場合はタスクを作成(deadlineが今よりも後の場合のみ) + if ( + event.data.after.data().deadline.toDate().getTime() <= + new Date().getTime() + ) { + return; + } + + // taskを作成 + try { + const postData = { + goalId: event.params.goalId, + marginTime, + }; + createTask( + tasksClient.queuePath(projectId, region, queue), + postData, + event.data.after.data().deadline.toDate() + ); + } catch (error) { + logger.error("Error scheduling task:", error); + } + } else if ( + event.data.before.data().post === null && + event.data.after.data().post === null + ) { + // deadlineが変更された場合はタスクを削除して再作成 + if (event.data.before.data() === event.data.after.data()) { + return; + } + try { + // taskを削除 + const goalId = event.params.goalId; + const queuePath = tasksClient.queuePath(projectId, region, queue); + await deleteTask(queuePath, goalId); + + const postData = { + goalId: event.params.goalId, + marginTime, + }; + // taskを作成 + createTask( + queuePath, + postData, + event.data.after.data().deadline.toDate() + ); + } catch (error) { + logger.error("Error updating task:", error); + } } } ); -// 目標を完了した時にtasksから通知予定を削除する -export const deleteTasksOnPostCreate = onDocumentUpdated( - { - region: region, - document: "goal/{goalId}", - }, +// 目標を削除した時にtasksから通知予定を削除する +export const deleteTasksOnGoalDelete = onDocumentDeleted( + { region: region, document: "goal/{goalId}" }, async (event) => { if (process.env.NODE_ENV !== "production") { return; @@ -119,16 +215,10 @@ export const deleteTasksOnPostCreate = onDocumentUpdated( return; } - if (event.data.after.data().post !== null) { - return; - } - try { const goalId = event.params.goalId; const queuePath = tasksClient.queuePath(projectId, region, queue); - const taskName = `${queuePath}/tasks/${goalId}`; // goalIdが名前になっているタスクを削除 - await tasksClient.deleteTask({ name: taskName }); - logger.info("Task deleted for goalId:", goalId); + await deleteTask(queuePath, goalId); } catch (error) { logger.error("Error deleting task:", error); }