feat: toggle checklist item completed state

This commit is contained in:
Henry
2025-08-09 22:14:56 +01:00
parent a4cc922d05
commit a3f0809507
6 changed files with 495 additions and 62 deletions

View File

@@ -71,14 +71,6 @@ export const checklistRouter = createTRPCRouter({
code: "INTERNAL_SERVER_ERROR",
});
// await cardActivityRepo.create(ctx.db, {
// type: "card.updated.checklist.added" as const,
// cardId: card.id,
// checklistId: newChecklist.id,
// toChecklist: newChecklist.title,
// createdBy: userId,
// });
return newChecklist;
}),
createItem: protectedProcedure
@@ -139,6 +131,116 @@ export const checklistRouter = createTRPCRouter({
return newChecklistItem;
}),
updateItem: protectedProcedure
.meta({
openapi: {
summary: "Update a checklist item",
method: "PUT",
path: "/checklists/items/{checklistItemPublicId}",
description: "Updates a checklist item (title/completed)",
tags: ["Cards"],
protect: true,
},
})
.input(
z.object({
checklistItemPublicId: z.string().length(12),
title: z.string().min(1).max(500).optional(),
completed: z.boolean().optional(),
}),
)
.output(checklistItemSchema)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const item = await checklistRepo.getChecklistItemByPublicIdWithChecklist(
ctx.db,
input.checklistItemPublicId,
);
if (!item)
throw new TRPCError({
message: `Checklist item with public ID ${input.checklistItemPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(
ctx.db,
userId,
item.checklist.card.list.board.workspace.id,
);
const updated = await checklistRepo.updateItemById(ctx.db, {
id: item.id,
title: input.title,
completed: input.completed,
});
if (!updated)
throw new TRPCError({
message: `Failed to update checklist item`,
code: "INTERNAL_SERVER_ERROR",
});
return updated;
}),
deleteItem: protectedProcedure
.meta({
openapi: {
summary: "Delete a checklist item",
method: "DELETE",
path: "/checklists/items/{checklistItemPublicId}",
description: "Deletes a checklist item",
tags: ["Cards"],
protect: true,
},
})
.input(z.object({ checklistItemPublicId: z.string().length(12) }))
.output(z.object({ success: z.boolean() }))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const item = await checklistRepo.getChecklistItemByPublicIdWithChecklist(
ctx.db,
input.checklistItemPublicId,
);
if (!item)
throw new TRPCError({
message: `Checklist item with public ID ${input.checklistItemPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(
ctx.db,
userId,
item.checklist.card.list.board.workspace.id,
);
const deleted = await checklistRepo.softDeleteItemById(ctx.db, {
id: item.id,
deletedAt: new Date(),
deletedBy: userId,
});
if (!deleted)
throw new TRPCError({
message: `Failed to delete item`,
code: "INTERNAL_SERVER_ERROR",
});
return { success: true };
}),
// update: protectedProcedure
// .meta({
// openapi: {

View File

@@ -6,6 +6,7 @@ import {
cards,
cardsToLabels,
cardToWorkspaceMembers,
checklistItems,
checklists,
labels,
lists,
@@ -316,6 +317,7 @@ export const getWithListAndMembersByPublicId = async (
completed: true,
index: true,
},
where: isNull(checklistItems.deletedAt),
},
},
},

View File

@@ -106,3 +106,68 @@ export const getChecklistByPublicId = async (
return checklist;
};
export const getChecklistItemByPublicIdWithChecklist = async (
db: dbClient,
checklistItemPublicId: string,
) => {
const item = await db.query.checklistItems.findFirst({
where: and(
eq(checklistItems.publicId, checklistItemPublicId),
isNull(checklistItems.deletedAt),
),
with: {
checklist: {
with: {
card: {
with: {
list: {
with: {
board: {
with: { workspace: true },
},
},
},
},
},
},
},
},
});
return item;
};
export const updateItemById = async (
db: dbClient,
args: { id: number; title?: string; completed?: boolean },
) => {
const [result] = await db
.update(checklistItems)
.set({
...(args.title !== undefined ? { title: args.title } : {}),
...(args.completed !== undefined ? { completed: args.completed } : {}),
updatedAt: new Date(),
})
.where(eq(checklistItems.id, args.id))
.returning({
publicId: checklistItems.publicId,
title: checklistItems.title,
completed: checklistItems.completed,
});
return result;
};
export const softDeleteItemById = async (
db: dbClient,
args: { id: number; deletedAt: Date; deletedBy: string },
) => {
const [result] = await db
.update(checklistItems)
.set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.where(eq(checklistItems.id, args.id))
.returning({ id: checklistItems.id });
return result;
};