diff --git a/packages/api/src/routers/card.ts b/packages/api/src/routers/card.ts index 26064840..57929f35 100644 --- a/packages/api/src/routers/card.ts +++ b/packages/api/src/routers/card.ts @@ -733,11 +733,6 @@ export const cardRouter = createTRPCRouter({ code: "INTERNAL_SERVER_ERROR", }); - await cardRepo.shiftIndex(ctx.supabaseClient, { - listId: card.list.id, - cardIndex: card.index, - }); - await cardActivityRepo.create(ctx.db, { type: "card.archived", cardId: card.id, diff --git a/packages/api/src/routers/list.ts b/packages/api/src/routers/list.ts index 4615153a..a5f51b08 100644 --- a/packages/api/src/routers/list.ts +++ b/packages/api/src/routers/list.ts @@ -133,11 +133,6 @@ export const listRouter = createTRPCRouter({ await activityRepo.bulkCreate(ctx.db, activities); - await listRepo.shiftIndex(ctx.supabaseClient, { - boardId: list.boardId, - listIndex: list.index, - }); - return { success: true }; }), update: protectedProcedure diff --git a/packages/db/seed.sql b/packages/db/seed.sql index bbbce7d2..c961732a 100644 --- a/packages/db/seed.sql +++ b/packages/db/seed.sql @@ -1,21 +1,3 @@ -CREATE OR REPLACE FUNCTION shift_list_index(board_id BIGINT, list_index INT) -RETURNS VOID -LANGUAGE SQL -AS $$ - UPDATE list - SET index = index - 1 - WHERE "boardId" = board_id AND index > list_index AND "deletedAt" IS NULL; -$$; - -CREATE OR REPLACE FUNCTION shift_card_index(list_id BIGINT, card_index INT) -RETURNS VOID -LANGUAGE SQL -AS $$ - UPDATE card - SET index = index - 1 - WHERE "listId" = list_id AND index > card_index AND "deletedAt" IS NULL; -$$; - CREATE OR REPLACE FUNCTION is_workspace_admin(user_id UUID, workspace_id BIGINT) RETURNS BOOLEAN LANGUAGE SQL diff --git a/packages/db/src/repository/card.repo.ts b/packages/db/src/repository/card.repo.ts index 3213146a..bb37c1ed 100644 --- a/packages/db/src/repository/card.repo.ts +++ b/packages/db/src/repository/card.repo.ts @@ -529,7 +529,7 @@ export const reorder = async ( const countExpr = sql`COUNT(*)`.mapWith(Number); - const duplicateIndices = await db + const duplicateIndices = await tx .select({ index: cards.index, count: countExpr, @@ -567,22 +567,6 @@ export const reorder = async ( }); }; -// Again should be handled in update transaction -export const shiftIndex = async ( - db: SupabaseClient, - args: { - listId: number; - cardIndex: number; - }, -) => { - const { data } = await db.rpc("shift_card_index", { - list_id: args.listId, - card_index: args.cardIndex, - }); - - return data; -}; - export const softDelete = async ( db: dbClient, args: { @@ -591,15 +575,46 @@ export const softDelete = async ( deletedBy: string; }, ) => { - const [result] = await db - .update(cards) - .set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) - .where(eq(cards.id, args.cardId)) - .returning({ - id: cards.id, - }); + return db.transaction(async (tx) => { + const [result] = await tx + .update(cards) + .set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) + .where(eq(cards.id, args.cardId)) + .returning({ + id: cards.id, + listId: cards.listId, + index: cards.index, + }); - return result; + if (!result) + throw new Error(`Unable to soft delete card ID ${args.cardId}`); + + await tx.execute(sql` + UPDATE card + SET index = index - 1 + WHERE "listId" = ${result.listId} AND index > ${result.index} AND "deletedAt" IS NULL; + `); + + const countExpr = sql`COUNT(*)`.mapWith(Number); + + const duplicateIndices = await tx + .select({ + index: cards.index, + count: countExpr, + }) + .from(cards) + .where(and(eq(cards.listId, result.listId), isNull(cards.deletedAt))) + .groupBy(cards.listId, cards.index) + .having(gt(countExpr, 1)); + + if (duplicateIndices.length > 0) { + throw new Error( + `Duplicate indices found after soft deleting ${result.id}`, + ); + } + + return result; + }); }; export const softDeleteAllByListIds = async ( diff --git a/packages/db/src/repository/list.repo.ts b/packages/db/src/repository/list.repo.ts index 53014b41..a4052739 100644 --- a/packages/db/src/repository/list.repo.ts +++ b/packages/db/src/repository/list.repo.ts @@ -122,7 +122,7 @@ export const reorder = async ( const countExpr = sql`COUNT(*)`.mapWith(Number); - const duplicateIndices = await db + const duplicateIndices = await tx .select({ index: lists.index, count: countExpr, @@ -150,21 +150,6 @@ export const reorder = async ( }); }; -export const shiftIndex = async ( - db: SupabaseClient, - args: { - boardId: number; - listIndex: number; - }, -) => { - const { data } = await db.rpc("shift_list_index", { - board_id: args.boardId, - list_index: args.listIndex, - }); - - return data; -}; - export const softDeleteAllByBoardId = async ( db: dbClient, args: { @@ -192,13 +177,44 @@ export const softDeleteById = async ( deletedBy: string; }, ) => { - const [updatedList] = await db - .update(lists) - .set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) - .where(and(eq(lists.id, args.listId), isNull(lists.deletedAt))) - .returning({ - id: lists.id, - }); + return db.transaction(async (tx) => { + const [result] = await tx + .update(lists) + .set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) + .where(and(eq(lists.id, args.listId), isNull(lists.deletedAt))) + .returning({ + id: lists.id, + index: lists.index, + boardId: lists.boardId, + }); - return updatedList; + if (!result) + throw new Error(`Unable to soft delete list ID ${args.listId}`); + + await tx.execute(sql` + UPDATE list + SET index = index - 1 + WHERE "boardId" = ${result.boardId} AND index > ${result.index} AND "deletedAt" IS NULL; + `); + + const countExpr = sql`COUNT(*)`.mapWith(Number); + + const duplicateIndices = await tx + .select({ + index: lists.index, + count: countExpr, + }) + .from(lists) + .where(and(eq(lists.boardId, result.boardId), isNull(lists.deletedAt))) + .groupBy(lists.index) + .having(gt(countExpr, 1)); + + if (duplicateIndices.length > 0) { + throw new Error( + `Duplicate indices found after reordering in board ${result.boardId}`, + ); + } + + return result; + }); };