Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

目標完了時にCloud Tasksの通知が削除されない問題を修正 #175

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const firestore = onRequest({ region: region }, async (req, res) => {
export {
createTasksOnGoalCreate,
deleteTasksOnGoalDelete,
deleteTasksOnPostCreate,
updateTasksOnPostUpdate,
} from "./tasks";

// テスト用API
Expand Down
190 changes: 140 additions & 50 deletions functions/src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}" },
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}
Expand Down
Loading