feat: add workspace member assertion to list router
This commit is contained in:
@@ -81,18 +81,18 @@ export const boardRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspaceId = await boardRepo.getWorkspaceIdByBoardPublicId(
|
const board = await boardRepo.getWorkspaceAndBoardIdByBoardPublicId(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
input.boardPublicId,
|
input.boardPublicId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!workspaceId)
|
if (!board)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
message: `Board with public ID ${input.boardPublicId} not found`,
|
message: `Board with public ID ${input.boardPublicId} not found`,
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertUserInWorkspace(ctx.db, userId, workspaceId);
|
await assertUserInWorkspace(ctx.db, userId, board.workspaceId);
|
||||||
|
|
||||||
const result = await boardRepo.getByPublicId(
|
const result = await boardRepo.getByPublicId(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
@@ -235,18 +235,18 @@ export const boardRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspaceId = await boardRepo.getWorkspaceIdByBoardPublicId(
|
const board = await boardRepo.getWorkspaceAndBoardIdByBoardPublicId(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
input.boardPublicId,
|
input.boardPublicId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!workspaceId)
|
if (!board)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
message: `Board with public ID ${input.boardPublicId} not found`,
|
message: `Board with public ID ${input.boardPublicId} not found`,
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertUserInWorkspace(ctx.db, userId, workspaceId);
|
await assertUserInWorkspace(ctx.db, userId, board.workspaceId);
|
||||||
|
|
||||||
const result = await boardRepo.update(ctx.db, {
|
const result = await boardRepo.update(ctx.db, {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
|
|||||||
@@ -218,18 +218,15 @@ export const importRouter = createTRPCRouter({
|
|||||||
.filter((label) => !!label.sourceId);
|
.filter((label) => !!label.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
let listIndex = 0;
|
|
||||||
|
|
||||||
for (const list of formattedData.lists) {
|
for (const list of formattedData.lists) {
|
||||||
const newList = await listRepo.create(ctx.db, {
|
const newList = await listRepo.create(ctx.db, {
|
||||||
name: list.name,
|
name: list.name,
|
||||||
createdBy: userId,
|
createdBy: userId,
|
||||||
boardId: newBoardId,
|
boardId: newBoardId,
|
||||||
index: listIndex,
|
|
||||||
importId: newImportId,
|
importId: newImportId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newListId = newList?.id;
|
const newListId = newList.id;
|
||||||
|
|
||||||
if (list.cards.length && newListId) {
|
if (list.cards.length && newListId) {
|
||||||
const cardsInsert = list.cards.map((card, index) => ({
|
const cardsInsert = list.cards.map((card, index) => ({
|
||||||
@@ -244,7 +241,7 @@ export const importRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const newCards = await cardRepo.bulkCreate(ctx.db, cardsInsert);
|
const newCards = await cardRepo.bulkCreate(ctx.db, cardsInsert);
|
||||||
|
|
||||||
if (!newCards?.length)
|
if (!newCards.length)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
message: "Failed to create new cards",
|
message: "Failed to create new cards",
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
@@ -302,8 +299,6 @@ export const importRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boardsCreated++;
|
boardsCreated++;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as activityRepo from "@kan/db/repository/cardActivity.repo";
|
|||||||
import * as listRepo from "@kan/db/repository/list.repo";
|
import * as listRepo from "@kan/db/repository/list.repo";
|
||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { assertUserInWorkspace } from "../utils/auth";
|
||||||
|
|
||||||
export const listRouter = createTRPCRouter({
|
export const listRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@@ -36,7 +37,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
const board = await boardRepo.getWithLatestListIndexByPublicId(
|
const board = await boardRepo.getWorkspaceAndBoardIdByBoardPublicId(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
input.boardPublicId,
|
input.boardPublicId,
|
||||||
);
|
);
|
||||||
@@ -47,14 +48,12 @@ export const listRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
const latestListIndex = board.lists[0]?.index;
|
await assertUserInWorkspace(ctx.db, userId, board.workspaceId);
|
||||||
|
|
||||||
const result = await listRepo.create(ctx.db, {
|
const result = await listRepo.create(ctx.db, {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
createdBy: userId,
|
createdBy: userId,
|
||||||
boardId: board.id,
|
boardId: board.id,
|
||||||
index:
|
|
||||||
(latestListIndex ?? latestListIndex === 0) ? latestListIndex + 1 : 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
@@ -91,7 +90,10 @@ export const listRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
const list = await listRepo.getByPublicId(ctx.db, input.listPublicId);
|
const list = await listRepo.getWorkspaceAndListIdByListPublicId(
|
||||||
|
ctx.db,
|
||||||
|
input.listPublicId,
|
||||||
|
);
|
||||||
|
|
||||||
if (!list)
|
if (!list)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -99,6 +101,8 @@ export const listRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await assertUserInWorkspace(ctx.db, userId, list.workspaceId);
|
||||||
|
|
||||||
const deletedAt = new Date();
|
const deletedAt = new Date();
|
||||||
|
|
||||||
const deletedList = await listRepo.softDeleteById(ctx.db, {
|
const deletedList = await listRepo.softDeleteById(ctx.db, {
|
||||||
@@ -131,7 +135,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
cardId: card.id,
|
cardId: card.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await activityRepo.bulkCreate(ctx.db, activities);
|
if (activities.length) await activityRepo.bulkCreate(ctx.db, activities);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}),
|
}),
|
||||||
@@ -160,6 +164,27 @@ export const listRouter = createTRPCRouter({
|
|||||||
>(),
|
>(),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const userId = ctx.user?.id;
|
||||||
|
|
||||||
|
if (!userId)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `User not authenticated`,
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = await listRepo.getWorkspaceAndListIdByListPublicId(
|
||||||
|
ctx.db,
|
||||||
|
input.listPublicId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!list)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `List with public ID ${input.listPublicId} not found`,
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
});
|
||||||
|
|
||||||
|
await assertUserInWorkspace(ctx.db, userId, list.workspaceId);
|
||||||
|
|
||||||
let result: { name: string; publicId: string } | undefined;
|
let result: { name: string; publicId: string } | undefined;
|
||||||
|
|
||||||
if (input.name) {
|
if (input.name) {
|
||||||
|
|||||||
@@ -453,16 +453,17 @@ export const isSlugUnique = async (
|
|||||||
return result === undefined;
|
return result === undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWorkspaceIdByBoardPublicId = async (
|
export const getWorkspaceAndBoardIdByBoardPublicId = async (
|
||||||
db: dbClient,
|
db: dbClient,
|
||||||
boardPublicId: string,
|
boardPublicId: string,
|
||||||
) => {
|
) => {
|
||||||
const result = await db.query.boards.findFirst({
|
const result = await db.query.boards.findFirst({
|
||||||
columns: {
|
columns: {
|
||||||
|
id: true,
|
||||||
workspaceId: true,
|
workspaceId: true,
|
||||||
},
|
},
|
||||||
where: eq(boards.publicId, boardPublicId),
|
where: eq(boards.publicId, boardPublicId),
|
||||||
});
|
});
|
||||||
|
|
||||||
return result?.workspaceId;
|
return result;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,27 +10,62 @@ export const create = async (
|
|||||||
name: string;
|
name: string;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
boardId: number;
|
boardId: number;
|
||||||
index: number;
|
|
||||||
importId?: number;
|
importId?: number;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const [result] = await db
|
return db.transaction(async (tx) => {
|
||||||
.insert(lists)
|
const list = await tx.query.lists.findFirst({
|
||||||
.values({
|
columns: {
|
||||||
publicId: generateUID(),
|
id: true,
|
||||||
name: listInput.name,
|
boardId: true,
|
||||||
createdBy: listInput.createdBy,
|
index: true,
|
||||||
boardId: listInput.boardId,
|
},
|
||||||
index: listInput.index,
|
where: and(eq(lists.boardId, listInput.boardId), isNull(lists.deletedAt)),
|
||||||
importId: listInput.importId,
|
orderBy: [desc(lists.index)],
|
||||||
})
|
|
||||||
.returning({
|
|
||||||
id: lists.id,
|
|
||||||
publicId: lists.publicId,
|
|
||||||
name: lists.name,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
const index = list?.index ? list.index + 1 : 0;
|
||||||
|
|
||||||
|
const [result] = await tx
|
||||||
|
.insert(lists)
|
||||||
|
.values({
|
||||||
|
publicId: generateUID(),
|
||||||
|
name: listInput.name,
|
||||||
|
createdBy: listInput.createdBy,
|
||||||
|
boardId: listInput.boardId,
|
||||||
|
index,
|
||||||
|
importId: listInput.importId,
|
||||||
|
})
|
||||||
|
.returning({
|
||||||
|
id: lists.id,
|
||||||
|
publicId: lists.publicId,
|
||||||
|
boardId: lists.boardId,
|
||||||
|
name: lists.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
throw new Error(`Failed to create list for board ${listInput.boardId}`);
|
||||||
|
|
||||||
|
const countExpr = sql<number>`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;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getByPublicId = async (db: dbClient, listPublicId: string) => {
|
export const getByPublicId = async (db: dbClient, listPublicId: string) => {
|
||||||
@@ -207,6 +242,8 @@ export const softDeleteById = async (
|
|||||||
.groupBy(lists.index)
|
.groupBy(lists.index)
|
||||||
.having(gt(countExpr, 1));
|
.having(gt(countExpr, 1));
|
||||||
|
|
||||||
|
console.log(duplicateIndices);
|
||||||
|
|
||||||
if (duplicateIndices.length > 0) {
|
if (duplicateIndices.length > 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Duplicate indices found after reordering in board ${result.boardId}`,
|
`Duplicate indices found after reordering in board ${result.boardId}`,
|
||||||
@@ -216,3 +253,24 @@ export const softDeleteById = async (
|
|||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getWorkspaceAndListIdByListPublicId = async (
|
||||||
|
db: dbClient,
|
||||||
|
listPublicId: string,
|
||||||
|
) => {
|
||||||
|
const result = await db.query.lists.findFirst({
|
||||||
|
columns: { id: true },
|
||||||
|
where: and(eq(lists.publicId, listPublicId), isNull(lists.deletedAt)),
|
||||||
|
with: {
|
||||||
|
board: {
|
||||||
|
columns: {
|
||||||
|
workspaceId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return result
|
||||||
|
? { id: result.id, workspaceId: result.board.workspaceId }
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user