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

Add subtasks APIs (queries + mutations) #45

Merged
merged 12 commits into from
Mar 22, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Task" ADD COLUMN "subtasksOrder" INTEGER[];
2 changes: 2 additions & 0 deletions apps/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ model Task {
date DateTime @db.Date
/// The length of time the task is expected to take.
durationInMinutes Int?
/// Order of the subtasks in the task
subtasksOrder Int[]

/// The item linked to the task
item Item? @relation(fields: [itemId], references: [id])
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/graphql/Day.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const DayType = builder.prismaNode("Day", {
const order = dayInfo?.tasksOrder ?? day.tasksOrder; // day.tasksOrder might be outdated if the tasksOrder was updated in another request
const tasks = await prisma.task.findMany({
...query,
where: { date: day.date },
where: { date: day.date, parentTaskId: null },
});
const tasksOrdered = tasks.sort((a, b) => {
return order.indexOf(a.id) - order.indexOf(b.id);
Expand Down
144 changes: 140 additions & 4 deletions apps/server/src/graphql/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DayType } from "./Day";
import { dayjs } from "@flowdev/server/src/utils/dayjs";
import { getPlugins } from "@flowdev/server/src/utils/getPlugins";
import { DateFilter, DateTimeFilter } from "./PrismaFilters";
import { GraphQLError } from "graphql";

// -------------- Task types --------------

Expand All @@ -28,6 +29,13 @@ export const TaskType = builder.prismaNode("Task", {
}),
tags: t.relation("tags"),
pluginDatas: t.relation("pluginDatas"),
subtasks: t.relation("subtasks", {
resolve: async (query, task) => {
const order = task.subtasksOrder ?? [];
const subtasks = await prisma.task.findMany({ ...query, where: { parentTaskId: task.id } });
return subtasks.sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
},
}),
}),
});

Expand Down Expand Up @@ -333,7 +341,19 @@ Any other scenario is not possible by nature of the app, where tasks:
// and the position of the task in the day
await tx.task.update({
where: { id: task.id },
data: { status: newStatus, completedAt: newStatus === "DONE" ? new Date() : null },
data: {
status: newStatus,
completedAt: newStatus === "DONE" ? new Date() : null,
subtasks: {
updateMany: {
where: { parentTaskId: task.id },
data: {
status: newStatus,
completedAt: newStatus === "DONE" ? new Date() : null,
},
},
},
},
});
const newTasksOrder = originalDay.tasksOrder.filter((id) => id !== task.id);
if (newStatus === "TODO") {
Expand All @@ -343,9 +363,7 @@ Any other scenario is not possible by nature of the app, where tasks:
}
await tx.day.update({
where: { date: startOfToday },
data: {
tasksOrder: { set: newTasksOrder },
},
data: { tasksOrder: { set: newTasksOrder } },
});
days.push(task.date);
} else if (task.date > endOfToday && (newStatus === "DONE" || newStatus === "CANCELED")) {
Expand All @@ -362,6 +380,16 @@ Any other scenario is not possible by nature of the app, where tasks:
create: { date: startOfToday },
},
},
subtasks: {
updateMany: {
where: { parentTaskId: task.id },
data: {
status: newStatus,
completedAt: newStatus === "DONE" ? new Date() : null,
date: startOfToday,
},
},
},
},
});
await tx.day.update({
Expand All @@ -388,6 +416,16 @@ Any other scenario is not possible by nature of the app, where tasks:
create: { date: startOfToday },
},
},
subtasks: {
updateMany: {
where: { parentTaskId: task.id },
data: {
status: newStatus,
completedAt: null,
date: startOfToday,
},
},
},
},
select: {
day: {
Expand Down Expand Up @@ -418,6 +456,16 @@ Any other scenario is not possible by nature of the app, where tasks:
data: {
status: newStatus,
completedAt: newStatus === "DONE" ? dayjs(task.date).endOf("day").toDate() : null,
subtasks: {
updateMany: {
where: { parentTaskId: task.id },
data: {
status: newStatus,
completedAt:
newStatus === "DONE" ? dayjs(task.date).endOf("day").toDate() : null,
},
},
},
},
});
days.push(task.date);
Expand Down Expand Up @@ -524,6 +572,16 @@ When the task is:
? {
status: newStatus,
completedAt: newStatus === "DONE" ? dayjs(newDate).endOf("day").toDate() : null,
subtasks: {
updateMany: {
where: { parentTaskId: task.id },
data: {
status: newStatus,
completedAt:
newStatus === "DONE" ? dayjs(newDate).endOf("day").toDate() : null,
},
},
},
}
: {}),
},
Expand Down Expand Up @@ -559,3 +617,81 @@ When the task is:
},
}),
);

// -------------------- Task subtasks mutations --------------------

builder.mutationField("createSubtask", (t) =>
t.prismaFieldWithInput({
type: "Task",
description: `Create a new subtask.`,
input: {
title: t.input.string({ required: true, description: "The title of the subtask." }),
parentTaskId: t.input.globalID({
required: true,
description: "The Relay ID of the parent task.",
}),
},
resolve: async (query, _, args) => {
const parentTask = await prisma.task.findUnique({
where: { id: parseInt(args.input.parentTaskId.id) },
});
if (!parentTask) {
throw new GraphQLError(`Parent task with ID ${args.input.parentTaskId.id} not found.`, {
extensions: {
code: "PARENT_TASK_NOT_FOUND",
userFriendlyMessage:
"The parent task was not found. Please try refreshing the page and try again.",
},
});
}
return prisma.task.create({
...query,
data: {
title: args.input.title,
status: "TODO",
parentTask: { connect: { id: parseInt(args.input.parentTaskId.id) } },
day: { connect: { date: parentTask.date } },
},
});
},
}),
);

builder.mutationField("makeTaskSubtaskOf", (t) =>
t.prismaFieldWithInput({
type: "Task",
description: `Make a task a subtask of another task.`,
input: {
taskId: t.input.globalID({
required: true,
description: "The Relay ID of the task to update.",
}),
parentTaskId: t.input.globalID({
required: true,
description: "The Relay ID of the parent task.",
}),
},
resolve: async (query, _, args) => {
const parentTask = await prisma.task.findUnique({
where: { id: parseInt(args.input.parentTaskId.id) },
});
if (!parentTask) {
throw new GraphQLError(`Parent task with ID ${args.input.parentTaskId.id} not found.`, {
extensions: {
code: "PARENT_TASK_NOT_FOUND",
userFriendlyMessage:
"The parent task was not found. Please try refreshing the page and try again.",
},
});
}
return prisma.task.update({
...query,
where: { id: parseInt(args.input.taskId.id) },
data: {
day: { connect: { date: parentTask.date } },
parentTask: { connect: { id: parseInt(args.input.parentTaskId.id) } },
},
});
},
}),
);
Loading
Loading