chore: move interactions to repo directory

This commit is contained in:
Henry
2024-07-24 15:50:39 +01:00
parent e50991f72f
commit bfd23406dd
16 changed files with 11287 additions and 537 deletions

10129
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -61,7 +61,7 @@
"postcss": "^8.4.34", "postcss": "^8.4.34",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11", "prettier-plugin-tailwindcss": "^0.5.11",
"supabase": "^1.153.4", "supabase": "^1.187.3",
"tailwind": "^4.0.0", "tailwind": "^4.0.0",
"tailwind-scrollbar": "^3.0.5", "tailwind-scrollbar": "^3.0.5",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",

View File

@@ -8,18 +8,15 @@ import {
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
import * as userRepo from "~/server/db/repository/user.repo";
export const authRouter = createTRPCRouter({ export const authRouter = createTRPCRouter({
getUser: protectedProcedure.query(async ({ ctx }) => { getUser: protectedProcedure.query(async ({ ctx }) => {
if (!ctx.user?.id) return; if (!ctx.user?.id) return;
const { data } = await ctx.db const result = await userRepo.getById(ctx.db, ctx.user.id);
.from("user")
.select(`id, name, email`)
.eq("id", ctx.user.id)
.limit(1)
.single();
return data; return result;
}), }),
loginWithEmail: publicProcedure loginWithEmail: publicProcedure
.input(z.object({ email: z.string() })) .input(z.object({ email: z.string() }))

View File

@@ -1,88 +1,33 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import * as boardRepo from "~/server/db/repository/board.repo";
import * as cardRepo from "~/server/db/repository/card.repo";
import * as listRepo from "~/server/db/repository/list.repo";
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
export const boardRouter = createTRPCRouter({ export const boardRouter = createTRPCRouter({
all: protectedProcedure all: protectedProcedure
.input(z.object({ workspacePublicId: z.string().min(12) })) .input(z.object({ workspacePublicId: z.string().min(12) }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const workspace = await ctx.db const workspace = await workspaceRepo.getByPublicId(
.from("workspace") ctx.db,
.select(`id`) input.workspacePublicId,
.eq("publicId", input.workspacePublicId) );
.limit(1)
.single();
if (!workspace.data) return; if (!workspace) return;
const { data } = await ctx.db const result = boardRepo.getAllByWorkspaceId(ctx.db, workspace.id);
.from("board")
.select(`publicId, name`)
.is("deletedAt", null)
.eq("workspaceId", workspace.data.id);
return data; return result;
}), }),
byId: protectedProcedure byId: protectedProcedure
.input(z.object({ id: z.string().min(12) })) .input(z.object({ boardPublicId: z.string().min(12) }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const { data } = await ctx.db const result = await boardRepo.getByPublicId(ctx.db, input.boardPublicId);
.from("board")
.select(
`
publicId,
name,
workspace (
publicId,
members:workspace_members (
publicId,
user (
name
)
)
),
labels:label (
publicId,
name,
colourCode
),
lists:list (
publicId,
name,
boardId,
index,
cards:card (
publicId,
title,
description,
listId,
index,
labels:label (
publicId,
name,
colourCode
),
members:workspace_members (
publicId,
user (
name
)
)
)
)
`,
)
.eq("publicId", input.id)
.is("deletedAt", null)
.is("lists.deletedAt", null)
.is("lists.cards.deletedAt", null)
.order("index", { foreignTable: "list", ascending: true })
.order("index", { foreignTable: "list.card", ascending: true })
.limit(1)
.single();
return data; return result;
}), }),
create: protectedProcedure create: protectedProcedure
.input( .input(
@@ -96,41 +41,35 @@ export const boardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const workspace = await ctx.db const workspace = await workspaceRepo.getByPublicId(
.from("workspace") ctx.db,
.select(`id`) input.workspacePublicId,
.eq("publicId", input.workspacePublicId) );
.limit(1)
.single();
if (!workspace.data) return; if (!workspace) return;
const { data } = await ctx.db const result = await boardRepo.create(ctx.db, {
.from("board") name: input.name,
.insert({ createdBy: userId,
publicId: generateUID(), workspaceId: workspace.id,
name: input.name, });
createdBy: userId,
workspaceId: workspace.data.id,
})
.select(`publicId, name`);
return data; return result;
}), }),
update: protectedProcedure update: protectedProcedure
.input( .input(
z.object({ z.object({
boardId: z.string().min(12), boardPublicId: z.string().min(12),
name: z.string().min(1), name: z.string().min(1),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const { data } = await ctx.db const result = await boardRepo.update(ctx.db, {
.from("board") name: input.name,
.update({ name: input.name }) boardPublicId: input.boardPublicId,
.eq("publicId", input.boardId); });
return data; return result;
}), }),
delete: protectedProcedure delete: protectedProcedure
.input( .input(
@@ -141,37 +80,34 @@ export const boardRouter = createTRPCRouter({
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id; const userId = ctx.user?.id;
const board = await ctx.db const board = await boardRepo.getWithListIdsByPublicId(
.from("board") ctx.db,
.select(`id, lists:list (id)`) input.boardPublicId,
.eq("publicId", input.boardPublicId) );
.limit(1) if (!board || !userId) return;
.single();
if (!board.data) return; const listIds = board.lists.map((list) => list.id);
const listIds = board.data.lists.map((list) => list.id);
const deletedAt = new Date().toISOString(); const deletedAt = new Date().toISOString();
await ctx.db await boardRepo.destroy(ctx.db, {
.from("board") boardId: board.id,
.update({ deletedAt, deletedBy: userId }) deletedAt,
.eq("id", board.data.id) deletedBy: userId,
.is("deletedAt", null); });
if (listIds.length) { if (listIds.length) {
await ctx.db await listRepo.destroyAllByBoardId(ctx.db, {
.from("list") boardId: board.id,
.update({ deletedAt, deletedBy: userId }) deletedAt,
.eq("boardId", board.data.id) deletedBy: userId,
.is("deletedAt", null); });
await ctx.db await cardRepo.destroyAllByListIds(ctx.db, {
.from("card") listIds,
.update({ deletedAt, deletedBy: userId }) deletedAt,
.in("listId", listIds) deletedBy: userId,
.is("deletedAt", null); });
} }
}), }),
}); });

View File

@@ -1,15 +1,19 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import * as cardRepo from "~/server/db/repository/card.repo";
import * as labelRepo from "~/server/db/repository/label.repo";
import * as listRepo from "~/server/db/repository/list.repo";
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
export const cardRouter = createTRPCRouter({ export const cardRouter = createTRPCRouter({
create: protectedProcedure create: protectedProcedure
.input( .input(
z.object({ z.object({
title: z.string().min(1), title: z.string().min(1),
listPublicId: z.string().min(12), listPublicId: z.string().min(12),
labelsPublicIds: z.array(z.string().min(12)), labelPublicIds: z.array(z.string().min(12)),
memberPublicIds: z.array(z.string().min(12)), memberPublicIds: z.array(z.string().min(12)),
}), }),
) )
@@ -18,63 +22,57 @@ export const cardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const list = await ctx.db const list = await listRepo.getWithCardsByPublicId(
.from("list") ctx.db,
.select(`id, cards:card (index)`) input.listPublicId,
.eq("publicId", input.listPublicId) );
.order("index", { foreignTable: "card", ascending: false })
.limit(1)
.single();
if (!list.data?.id) return; if (!list?.id) return;
const latestCard = list.data.cards.length && list.data.cards[0]; const latestCard = list.cards.length && list.cards[0];
const newCard = await ctx.db const newCard = await cardRepo.create(ctx.db, {
.from("card") title: input.title,
.insert({ createdBy: userId,
publicId: generateUID(), listId: list.id,
title: input.title, index: latestCard ? latestCard.index + 1 : 0,
createdBy: userId, });
listId: list.data.id,
index: latestCard ? latestCard.index + 1 : 0,
})
.select(`id`)
.limit(1)
.single();
const newCardId = newCard.data?.id; const newCardId = newCard?.id;
if (newCardId && input.labelsPublicIds.length) { if (newCardId && input.labelPublicIds.length) {
const labels = await ctx.db const labels = await labelRepo.getAllByPublicIds(
.from("label") ctx.db,
.select(`id`) input.labelPublicIds,
.eq("publicId", input.labelsPublicIds); );
if (!labels.data?.length) return; if (!labels?.length) return;
const labelsInsert = labels.data.map((label) => ({ const labelsInsert = labels.map((label) => ({
cardId: newCardId, cardId: newCardId,
labelId: label.id, labelId: label.id,
})); }));
await ctx.db.from("_card_labels").insert(labelsInsert); await cardRepo.bulkCreateCardLabelRelationships(ctx.db, labelsInsert);
} }
if (newCardId && input.memberPublicIds.length) { if (newCardId && input.memberPublicIds.length) {
const members = await ctx.db const members = await workspaceRepo.getAllMembersByPublicIds(
.from("workspace_members") ctx.db,
.select(`id`) input.memberPublicIds,
.eq("publicId", input.memberPublicIds); );
if (!members.data?.length) return; if (!members?.length) return;
const membersInsert = members.data.map((member) => ({ const membersInsert = members.map((member) => ({
cardId: newCardId, cardId: newCardId,
workspaceMemberId: member.id, workspaceMemberId: member.id,
})); }));
await ctx.db.from("_card_workspace_members").insert(membersInsert); await cardRepo.bulkCreateCardWorkspaceMemberRelationships(
ctx.db,
membersInsert,
);
} }
return newCard; return newCard;
@@ -91,44 +89,25 @@ export const cardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await cardRepo.getByPublicId(ctx.db, input.cardPublicId);
.from("card") const label = await labelRepo.getByPublicId(ctx.db, input.labelPublicId);
.select(`id`)
.eq("publicId", input.cardPublicId)
.limit(1)
.single();
const label = await ctx.db if (!card || !label) return;
.from("label")
.select(`id`)
.eq("publicId", input.labelPublicId)
.limit(1)
.single();
if (!card.data || !label.data) return; const cardLabelIds = { cardId: card.id, labelId: label.id };
const existingLabel = await ctx.db const existingLabel = await cardRepo.getCardLabelRelationship(
.from("_card_labels") ctx.db,
.select() cardLabelIds,
.eq("cardId", card.data.id) );
.eq("labelId", label.data.id)
.limit(1)
.single();
if (existingLabel.data) { if (existingLabel) {
await ctx.db await cardRepo.destroyCardLabelRelationship(ctx.db, cardLabelIds);
.from("_card_labels")
.delete()
.eq("cardId", card.data.id)
.eq("labelId", label.data.id);
return { newLabel: false }; return { newLabel: false };
} }
await ctx.db.from("_card_labels").insert({ await cardRepo.createCardLabelRelationship(ctx.db, cardLabelIds);
cardId: card.data.id,
labelId: label.data.id,
});
return { newLabel: true }; return { newLabel: true };
}), }),
@@ -144,105 +123,40 @@ export const cardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await cardRepo.getByPublicId(ctx.db, input.cardPublicId);
.from("card") const member = await workspaceRepo.getMemberByPublicId(
.select(`id`) ctx.db,
.eq("publicId", input.cardPublicId) input.workspaceMemberPublicId,
.limit(1) );
.single();
const member = await ctx.db if (!card || !member) return;
.from("workspace_members")
.select(`id`)
.eq("publicId", input.workspaceMemberPublicId)
.limit(1)
.single();
if (!card.data || !member.data) return; const cardMemberIds = { cardId: card.id, memberId: member.id };
const existingMember = await ctx.db const existingMember = await cardRepo.getCardMemberRelationship(
.from("_card_workspace_members") ctx.db,
.select() cardMemberIds,
.eq("cardId", card.data.id) );
.eq("workspaceMemberId", member.data.id)
.limit(1)
.single();
if (existingMember.data) { if (existingMember) {
await ctx.db await cardRepo.destroyCardMemberRelationship(ctx.db, cardMemberIds);
.from("_card_workspace_members")
.delete()
.eq("cardId", card.data.id)
.eq("workspaceMemberId", member.data.id);
return { newMember: false }; return { newMember: false };
} }
await ctx.db.from("_card_workspace_members").insert({ await cardRepo.createCardMemberRelationship(ctx.db, cardMemberIds);
cardId: card.data.id,
workspaceMemberId: member.data.id,
});
return { newMember: true }; return { newMember: true };
}), }),
byId: protectedProcedure byId: protectedProcedure
.input(z.object({ id: z.string().min(12) })) .input(z.object({ id: z.string().min(12) }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const { data } = await ctx.db const result = await cardRepo.getWithListAndMembersByPublicId(
.from("card") ctx.db,
.select( input.id,
` );
publicId,
title,
description,
labels:label (
publicId,
name,
colourCode
),
list (
publicId,
name,
board (
publicId,
name,
labels:label (
publicId,
colourCode,
name
),
lists:list (
publicId,
name
),
workspace (
publicId,
members:workspace_members (
publicId,
user (
id,
name
)
)
)
)
),
members:workspace_members (
publicId,
user (
id,
name
)
)
`,
)
.eq("publicId", input.id)
.is("deletedAt", null)
.is("list.board.lists.deletedAt", null)
.limit(1)
.single();
return data; return result;
}), }),
update: protectedProcedure update: protectedProcedure
.input( .input(
@@ -257,13 +171,13 @@ export const cardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const { data } = await ctx.db const result = cardRepo.update(
.from("card") ctx.db,
.update({ title: input.title, description: input.description }) { title: input.title, description: input.description },
.eq("publicId", input.cardId) { cardPublicId: input.cardId },
.is("deletedAt", null); );
return data; return result;
}), }),
delete: protectedProcedure delete: protectedProcedure
.input( .input(
@@ -276,30 +190,24 @@ export const cardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await cardRepo.getCardWithListByPublicId(
.from("card") ctx.db,
.select(`id, index, listId`) input.cardPublicId,
.eq("publicId", input.cardPublicId) );
.limit(1)
.single();
if (!card.data) return; if (!card || !card?.list?.id) return;
const deletedAt = new Date().toISOString(); const deletedAt = new Date().toISOString();
await ctx.db await cardRepo.destroy(ctx.db, {
.from("card") cardId: card.id,
.update({ deletedAt, deletedBy: userId }) deletedAt,
.eq("publicId", input.cardPublicId); deletedBy: userId,
});
await ctx.db await cardRepo.shiftIndex(ctx.db, {
.from("card") listId: card.list.id,
.update({ deletedAt, deletedBy: userId }) cardIndex: card.index,
.eq("publicId", input.cardPublicId);
await ctx.db.rpc("shift_card_index", {
list_id: card.data.listId,
card_index: card.data.index,
}); });
}), }),
reorder: protectedProcedure reorder: protectedProcedure
@@ -315,50 +223,43 @@ export const cardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await cardRepo.getCardWithListByPublicId(
.from("card") ctx.db,
.select(`id, index, list (id)`) input.cardId,
.eq("publicId", input.cardId) );
.is("deletedAt", null)
.limit(1)
.single();
if (!card.data) return; if (!card) return;
const currentList = card.data.list; const currentList = card.list;
const currentIndex = card.data.index; const currentIndex = card.index;
let newIndex = input.newIndex; let newIndex = input.newIndex;
const newList = await ctx.db const newList = await listRepo.getWithCardsByPublicId(
.from("list") ctx.db,
.select(`id, cards:card (index)`) input.newListId,
.eq("publicId", input.newListId) );
.is("deletedAt", null)
.order("index", { foreignTable: "card", ascending: false })
.limit(1)
.single();
if (!newList.data) return; if (!newList) return;
if (newIndex === undefined) { if (newIndex === undefined) {
const lastCardIndex = newList.data.cards.length const lastCardIndex = newList.cards.length
? newList.data.cards[0]?.index ? newList.cards[0]?.index
: undefined; : undefined;
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0; newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
} }
if (!currentList?.id || !newList?.data.id) return; if (!currentList?.id || !newList.id) return;
const { error } = await ctx.db.rpc("reorder_cards", { const result = await cardRepo.reorder(ctx.db, {
current_list_id: currentList.id, currentListId: currentList.id,
new_list_id: newList.data.id, newListId: newList.id,
current_index: currentIndex, currentIndex,
new_index: newIndex, newIndex,
card_id: card.data.id, cardId: card.id,
}); });
return { success: !!error }; return { success: !!result.error };
}), }),
}); });

View File

@@ -1,9 +1,13 @@
import { z } from "zod"; import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import * as boardRepo from "~/server/db/repository/board.repo";
import * as cardRepo from "~/server/db/repository/card.repo";
import * as importRepo from "~/server/db/repository/import.repo";
import * as listRepo from "~/server/db/repository/list.repo";
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
const TRELLO_API_URL = "https://api.trello.com/1"; const TRELLO_API_URL = "https://api.trello.com/1";
interface TrelloBoard { interface TrelloBoard {
@@ -87,31 +91,21 @@ export const importRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const newImport = await ctx.db const newImport = await importRepo.create(ctx.db, {
.from("import") source: "trello",
.insert({ createdBy: userId,
publicId: generateUID(), });
source: "trello",
createdBy: userId,
status: "started",
})
.select(`id`)
.limit(1)
.single();
const newImportId = newImport.data?.id; const newImportId = newImport?.id;
let boardsCreated = 0; let boardsCreated = 0;
const workspace = await ctx.db const workspace = await workspaceRepo.getByPublicId(
.from("workspace") ctx.db,
.select(`id`) input.workspacePublicId,
.eq("publicId", input.workspacePublicId) );
.is("deletedAt", null)
.limit(1)
.single();
if (!workspace.data) return; if (!workspace) return;
for (const boardId of input.boardIds) { for (const boardId of input.boardIds) {
const response = await fetch( const response = await fetch(
@@ -132,41 +126,29 @@ export const importRouter = createTRPCRouter({
})), })),
}; };
const newBoard = await ctx.db const newBoard = await boardRepo.create(ctx.db, {
.from("board") name: formattedData.name,
.insert({ createdBy: userId,
publicId: generateUID(), importId: newImportId,
name: data.name, workspaceId: workspace.id,
createdBy: userId, });
importId: newImportId,
workspaceId: workspace.data.id,
})
.select(`id`)
.limit(1)
.single();
const newBoardId = newBoard.data?.id; const newBoardId = newBoard?.id;
if (!newBoardId) return; if (!newBoardId) return;
let listIndex = 0; let listIndex = 0;
for (const list of formattedData.lists) { for (const list of formattedData.lists) {
const newList = await ctx.db const newList = await listRepo.create(ctx.db, {
.from("list") name: list.name,
.insert({ createdBy: userId,
publicId: generateUID(), boardId: newBoardId,
name: list.name, index: listIndex,
createdBy: userId, importId: newImportId,
boardId: newBoardId, });
index: listIndex,
importId: newImportId,
})
.select(`id`)
.limit(1)
.single();
const newListId = newList.data?.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) => ({
@@ -179,6 +161,8 @@ export const importRouter = createTRPCRouter({
importId: newImportId, importId: newImportId,
})); }));
await cardRepo.bulkCreate(ctx.db, cardsInsert);
await ctx.db.from("card").insert(cardsInsert); await ctx.db.from("card").insert(cardsInsert);
} }
@@ -188,10 +172,11 @@ export const importRouter = createTRPCRouter({
} }
if (boardsCreated > 0 && newImportId) { if (boardsCreated > 0 && newImportId) {
await ctx.db await importRepo.update(
.from("import") ctx.db,
.update({ status: "success" }) { status: "success" },
.eq("importId", newImportId); { importId: newImport.id },
);
} }
return { boardsCreated }; return { boardsCreated };

View File

@@ -1,8 +1,10 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import * as cardRepo from "~/server/db/repository/card.repo";
import * as labelRepo from "~/server/db/repository/label.repo";
export const labelRouter = createTRPCRouter({ export const labelRouter = createTRPCRouter({
create: protectedProcedure create: protectedProcedure
.input( .input(
@@ -17,36 +19,20 @@ export const labelRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await cardRepo.getCardWithListByPublicId(
.from("card") ctx.db,
.select(`id, list (boardId)`) input.cardPublicId,
.eq("publicId", input.cardPublicId) );
.is("deletedAt", null)
.limit(1)
.single();
if (!card.data?.list) return; if (!card?.list) return;
const newLabel = await ctx.db const result = await labelRepo.create(ctx.db, {
.from("label") name: input.name,
.insert({ colourCode: input.colourCode,
publicId: generateUID(), createdBy: userId,
name: input.name, boardId: card.list.boardId,
colourCode: input.colourCode,
createdBy: userId,
boardId: card.data.list.boardId,
})
.select(`id`)
.limit(1)
.single();
if (!newLabel.data) return;
await ctx.db.from("_card_labels").insert({
cardId: card.data.id,
labelId: newLabel.data.id,
}); });
return newLabel.data; return result;
}), }),
}); });

View File

@@ -1,8 +1,11 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import * as cardRepo from "~/server/db/repository/card.repo";
import * as boardRepo from "~/server/db/repository/board.repo";
import * as listRepo from "~/server/db/repository/list.repo";
export const listRouter = createTRPCRouter({ export const listRouter = createTRPCRouter({
create: protectedProcedure create: protectedProcedure
.input( .input(
@@ -16,31 +19,23 @@ export const listRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const board = await ctx.db const board = await boardRepo.getWithLatestListIndexByPublicId(
.from("board") ctx.db,
.select(`id, lists:list (index)`) input.boardPublicId,
.eq("publicId", input.boardPublicId) );
.order("index", { foreignTable: "list", ascending: false })
.is("list.deletedAt", null)
.limit(1)
.single();
if (!board.data) return; if (!board) return;
const latestListIndex = board.data.lists[0]?.index; const latestListIndex = board.lists[0]?.index;
const { data } = await ctx.db.from("list").insert({ const result = await listRepo.create(ctx.db, {
publicId: generateUID(),
name: input.name, name: input.name,
createdBy: userId, createdBy: userId,
boardId: board.data.id, boardId: board.id,
index: latestListIndex ? latestListIndex + 1 : 0, index: latestListIndex ? latestListIndex + 1 : 0,
}).select(` });
publicId,
name
`);
return data; return result;
}), }),
reorder: protectedProcedure reorder: protectedProcedure
.input( .input(
@@ -52,23 +47,18 @@ export const listRouter = createTRPCRouter({
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const list = await ctx.db const list = await listRepo.getByPublicId(ctx.db, input.listId);
.from("list")
.select(`id, boardId`)
.eq("publicId", input.listId)
.limit(1)
.single();
if (!list?.data) return; if (!list) return;
const { data } = await ctx.db.rpc("reorder_lists", { const result = listRepo.reorder(ctx.db, {
board_id: list.data.boardId, boardPublicId: list.boardId,
list_id: list.data.id, listPublicId: list.id,
current_index: input.currentIndex, currentIndex: input.currentIndex,
new_index: input.newIndex, newIndex: input.newIndex,
}); });
return data; return result;
}), }),
delete: protectedProcedure delete: protectedProcedure
.input( .input(
@@ -79,35 +69,28 @@ export const listRouter = createTRPCRouter({
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id; const userId = ctx.user?.id;
const list = await ctx.db const list = await listRepo.getByPublicId(ctx.db, input.listPublicId);
.from("list")
.select(`id, boardId, index`)
.eq("publicId", input.listPublicId)
.limit(1)
.single();
if (!list.data) return; if (!list || !userId) return;
const deletedAt = new Date().toISOString(); const deletedAt = new Date().toISOString();
await ctx.db await listRepo.destroyById(ctx.db, {
.from("list") listId: list.id,
.update({ deletedAt, deletedBy: userId }) deletedAt,
.eq("id", list.data.id) deletedBy: userId,
.is("deletedAt", null);
await ctx.db
.from("card")
.update({ deletedAt, deletedBy: userId })
.eq("listId", list.data.id)
.is("deletedAt", null);
const { data } = await ctx.db.rpc("shift_list_index", {
board_id: list.data.boardId,
list_index: list.data.index,
}); });
return data; await cardRepo.destroyAllByListIds(ctx.db, {
listIds: [list.id],
deletedAt,
deletedBy: userId,
});
await listRepo.shiftIndex(ctx.db, {
boardId: list.boardId,
listIndex: list.id,
});
}), }),
update: protectedProcedure update: protectedProcedure
.input( .input(
@@ -117,13 +100,10 @@ export const listRouter = createTRPCRouter({
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const { data } = await ctx.db const result = await listRepo.update(
.from("list") ctx.db,
.update({ name: input.name }) { name: input.name },
.eq("publicId", input.listPublicId) { listPublicId: input.listPublicId },
.is("deletedAt", null) );
.select(`publicId, name`);
return data;
}), }),
}); });

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
export const workspaceRouter = createTRPCRouter({ export const workspaceRouter = createTRPCRouter({
all: protectedProcedure.query(async ({ ctx }) => { all: protectedProcedure.query(async ({ ctx }) => {
@@ -9,47 +10,19 @@ export const workspaceRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const { data } = await ctx.db const result = await workspaceRepo.getAllByUserId(ctx.db, userId);
.from("workspace_members")
.select(
`
role,
workspace (
publicId,
name
)
`,
)
.eq("userId", userId)
.is("deletedAt", null);
return data; return result;
}), }),
byId: protectedProcedure byId: protectedProcedure
.input(z.object({ publicId: z.string().min(12) })) .input(z.object({ publicId: z.string().min(12) }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const { data } = await ctx.db const result = await workspaceRepo.getByPublicIdWithMembers(
.from("workspace") ctx.db,
.select( input.publicId,
` );
publicId,
members: workspace_members (
publicId,
role,
user (
id,
name,
email
)
)
`,
)
.eq("publicId", input.publicId)
.is("deletedAt", null)
.limit(1)
.single();
return data; return result;
}), }),
create: protectedProcedure create: protectedProcedure
.input( .input(
@@ -62,34 +35,12 @@ export const workspaceRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const workspace = await ctx.db const result = await workspaceRepo.create(ctx.db, {
.from("workspace") name: input.name,
.insert({ slug: input.name,
publicId: generateUID(),
name: input.name,
slug: input.name.toLowerCase(),
createdBy: userId,
})
.select(`id, publicId, name`)
.limit(1)
.single();
const newWorkspaceId = workspace.data?.id;
if (!newWorkspaceId) return;
await ctx.db.from("workspace_members").insert({
publicId: generateUID(),
userId,
workspaceId: newWorkspaceId,
createdBy: userId, createdBy: userId,
role: "admin",
}); });
const newWorkspace = { ...workspace.data }; return result;
delete newWorkspace.id;
return newWorkspace;
}), }),
}); });

View File

@@ -0,0 +1,162 @@
import { generateUID } from "~/utils/generateUID";
import { type Database } from "~/types/database.types";
import { type SupabaseClient } from "@supabase/supabase-js";
export const getAllByWorkspaceId = async (
db: SupabaseClient<Database>,
workspaceId: number,
) => {
const { data } = await db
.from("board")
.select(`publicId, name`)
.is("deletedAt", null)
.eq("workspaceId", workspaceId);
return data;
};
export const getByPublicId = async (
db: SupabaseClient<Database>,
boardPublicId: string,
) => {
const { data } = await db
.from("board")
.select(
`
publicId,
name,
workspace (
publicId,
members:workspace_members (
publicId,
user (
name
)
)
),
labels:label (
publicId,
name,
colourCode
),
lists:list (
publicId,
name,
boardId,
index,
cards:card (
publicId,
title,
description,
listId,
index,
labels:label (
publicId,
name,
colourCode
),
members:workspace_members (
publicId,
user (
name
)
)
)
)
`,
)
.eq("publicId", boardPublicId)
.is("deletedAt", null)
.is("lists.deletedAt", null)
.is("lists.cards.deletedAt", null)
.order("index", { foreignTable: "list", ascending: true })
.order("index", { foreignTable: "list.card", ascending: true })
.limit(1)
.single();
return data;
};
export const getWithListIdsByPublicId = async (
db: SupabaseClient<Database>,
boardPublicId: string,
) => {
const { data } = await db
.from("board")
.select(`id, lists:list (id)`)
.eq("publicId", boardPublicId)
.limit(1)
.single();
return data;
};
export const getWithLatestListIndexByPublicId = async (
db: SupabaseClient<Database>,
boardPublicId: string,
) => {
const { data } = await db
.from("board")
.select(`id, lists:list (index)`)
.eq("publicId", boardPublicId)
.order("index", { foreignTable: "list", ascending: false })
.is("list.deletedAt", null)
.limit(1)
.single();
return data;
};
export const create = async (
db: SupabaseClient<Database>,
boardInput: {
name: string;
createdBy: string;
workspaceId: number;
importId?: number;
},
) => {
const { data } = await db
.from("board")
.insert({
publicId: generateUID(),
name: boardInput.name,
createdBy: boardInput.createdBy,
workspaceId: boardInput.workspaceId,
importId: boardInput.importId,
})
.select(`id, publicId, name`)
.limit(1)
.single();
return data;
};
export const update = async (
db: SupabaseClient<Database>,
boardInput: { name: string; boardPublicId: string },
) => {
const { data } = await db
.from("board")
.update({ name: boardInput.name })
.eq("publicId", boardInput.boardPublicId);
return data;
};
export const destroy = async (
db: SupabaseClient<Database>,
args: {
boardId: number;
deletedAt: string;
deletedBy: string;
},
) => {
const result = db
.from("board")
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.eq("id", args.boardId)
.is("deletedAt", null);
return result;
};

View File

@@ -0,0 +1,329 @@
import { generateUID } from "~/utils/generateUID";
import { type Database } from "~/types/database.types";
import { type SupabaseClient } from "@supabase/supabase-js";
export const create = async (
db: SupabaseClient<Database>,
cardInput: {
title: string;
createdBy: string;
listId: number;
index: number;
},
) => {
const { data } = await db
.from("card")
.insert({
publicId: generateUID(),
title: cardInput.title,
createdBy: cardInput.createdBy,
listId: cardInput.listId,
index: cardInput.index,
})
.select(`id`)
.limit(1)
.single();
return data;
};
export const bulkCreateCardLabelRelationships = async (
db: SupabaseClient<Database>,
cardLabelRelationshipInput: {
cardId: number;
labelId: number;
}[],
) => {
const result = db.from("_card_labels").insert(cardLabelRelationshipInput);
return result;
};
export const bulkCreateCardWorkspaceMemberRelationships = async (
db: SupabaseClient<Database>,
cardWorkspaceMemberRelationshipInput: {
cardId: number;
workspaceMemberId: number;
}[],
) => {
const result = db
.from("_card_workspace_members")
.insert(cardWorkspaceMemberRelationshipInput);
return result;
};
export const update = async (
db: SupabaseClient<Database>,
cardInput: {
title: string;
description: string;
},
args: {
cardPublicId: string;
},
) => {
const { data } = await db
.from("card")
.update({ title: cardInput.title, description: cardInput.description })
.eq("publicId", args.cardPublicId)
.is("deletedAt", null);
return data;
};
export const destroy = async (
db: SupabaseClient<Database>,
args: {
cardId: number;
deletedAt: string;
deletedBy: string;
},
) => {
const result = await db
.from("card")
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.eq("publicId", args.cardId);
return result;
};
export const destroyAllByListIds = async (
db: SupabaseClient<Database>,
args: {
listIds: number[];
deletedAt: string;
deletedBy: string;
},
) => {
const result = await db
.from("card")
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.in("listId", args.listIds)
.is("deletedAt", null);
return result;
};
export const getCardWithListByPublicId = async (
db: SupabaseClient<Database>,
cardPublicId: string,
) => {
const { data } = await db
.from("card")
.select(`id, index, list (id, boardId)`)
.eq("publicId", cardPublicId)
.is("deletedAt", null)
.limit(1)
.single();
return data;
};
export const getByPublicId = async (
db: SupabaseClient<Database>,
cardPublicId: string,
) => {
const { data } = await db
.from("card")
.select(`id`)
.eq("publicId", cardPublicId)
.limit(1)
.single();
return data;
};
export const getCardLabelRelationship = async (
db: SupabaseClient<Database>,
args: { cardId: number; labelId: number },
) => {
const { data } = await db
.from("_card_labels")
.select()
.eq("cardId", args.cardId)
.eq("labelId", args.labelId)
.limit(1)
.single();
return data;
};
export const bulkCreate = async (
db: SupabaseClient<Database>,
cardInput: {
publicId: string;
title: string;
description: string;
createdBy: string;
listId: number;
index: number;
importId?: number;
}[],
) => {
const data = await db.from("card").insert(cardInput);
return data;
};
export const destroyCardLabelRelationship = async (
db: SupabaseClient<Database>,
args: { cardId: number; labelId: number },
) => {
const result = await db
.from("_card_labels")
.delete()
.eq("cardId", args.cardId)
.eq("labelId", args.labelId);
return result;
};
export const createCardLabelRelationship = async (
db: SupabaseClient<Database>,
cardLabelRelationshipInput: { cardId: number; labelId: number },
) => {
const { data } = await db.from("_card_labels").insert({
cardId: cardLabelRelationshipInput.cardId,
labelId: cardLabelRelationshipInput.labelId,
});
return data;
};
export const getCardMemberRelationship = async (
db: SupabaseClient<Database>,
args: { cardId: number; memberId: number },
) => {
const { data } = await db
.from("_card_workspace_members")
.select()
.eq("cardId", args.cardId)
.eq("workspaceMemberId", args.memberId)
.limit(1)
.single();
return data;
};
export const destroyCardMemberRelationship = async (
db: SupabaseClient<Database>,
args: { cardId: number; memberId: number },
) => {
const result = await db
.from("_card_workspace_members")
.delete()
.eq("cardId", args.cardId)
.eq("workspaceMemberId", args.memberId);
return result;
};
export const createCardMemberRelationship = async (
db: SupabaseClient<Database>,
cardMemberRelationshipInput: { cardId: number; memberId: number },
) => {
const { data } = await db.from("_card_workspace_members").insert({
cardId: cardMemberRelationshipInput.cardId,
workspaceMemberId: cardMemberRelationshipInput.memberId,
});
return data;
};
export const getWithListAndMembersByPublicId = async (
db: SupabaseClient<Database>,
cardPublicId: string,
) => {
const { data } = await db
.from("card")
.select(
`
publicId,
title,
description,
labels:label (
publicId,
name,
colourCode
),
list (
publicId,
name,
board (
publicId,
name,
labels:label (
publicId,
colourCode,
name
),
lists:list (
publicId,
name
),
workspace (
publicId,
members:workspace_members (
publicId,
user (
id,
name
)
)
)
)
),
members:workspace_members (
publicId,
user (
id,
name
)
)
`,
)
.eq("publicId", cardPublicId)
.is("deletedAt", null)
.is("list.board.lists.deletedAt", null)
.limit(1)
.single();
return data;
};
export const reorder = async (
db: SupabaseClient<Database>,
args: {
currentListId: number;
newListId: number;
currentIndex: number;
newIndex: number;
cardId: number;
},
) => {
const result = await db.rpc("reorder_cards", {
current_list_id: args.currentListId,
new_list_id: args.newListId,
current_index: args.currentIndex,
new_index: args.newIndex,
card_id: args.cardId,
});
return result;
};
export const shiftIndex = async (
db: SupabaseClient<Database>,
args: {
listId: number;
cardIndex: number;
},
) => {
const { data } = await db.rpc("shift_card_index", {
list_id: args.listId,
card_index: args.cardIndex,
});
return data;
};

View File

@@ -0,0 +1,37 @@
import { generateUID } from "~/utils/generateUID";
import { type Database } from "~/types/database.types";
import { type SupabaseClient } from "@supabase/supabase-js";
export const create = async (
db: SupabaseClient<Database>,
importInput: { source: string; createdBy: string },
) => {
const { data } = await db
.from("import")
.insert({
publicId: generateUID(),
source: "trello",
createdBy: importInput.createdBy,
status: "started",
})
.select(`id`)
.limit(1)
.single();
return data;
};
export const update = async (
db: SupabaseClient<Database>,
importInput: { status: "started" | "success" | "failed" },
args: { importId: number },
) => {
const { data } = await db
.from("import")
.update({ status: importInput.status })
.eq("importId", args.importId)
.limit(1)
.single();
return data;
};

View File

@@ -0,0 +1,61 @@
import { generateUID } from "~/utils/generateUID";
import { type Database } from "~/types/database.types";
import { type SupabaseClient } from "@supabase/supabase-js";
export const create = async (
db: SupabaseClient<Database>,
labelInput: {
name: string;
colourCode: string;
createdBy: string;
boardId: number;
cardId?: number;
},
) => {
const { data } = await db
.from("label")
.insert({
publicId: generateUID(),
name: labelInput.name,
colourCode: labelInput.colourCode,
createdBy: labelInput.createdBy,
boardId: labelInput.boardId,
})
.select(`id`)
.limit(1)
.single();
if (labelInput.cardId && data)
await db.from("_card_labels").insert({
cardId: labelInput.cardId,
labelId: data.id,
});
return data;
};
export const getAllByPublicIds = async (
db: SupabaseClient<Database>,
labelPublicIds: string[],
) => {
const { data } = await db
.from("label")
.select(`id`)
.eq("publicId", labelPublicIds);
return data;
};
export const getByPublicId = async (
db: SupabaseClient<Database>,
labelPublicId: string,
) => {
const { data } = await db
.from("label")
.select(`id`)
.eq("publicId", labelPublicId)
.limit(1)
.single();
return data;
};

View File

@@ -0,0 +1,154 @@
import { generateUID } from "~/utils/generateUID";
import { type Database } from "~/types/database.types";
import { type SupabaseClient } from "@supabase/supabase-js";
export const create = async (
db: SupabaseClient<Database>,
listInput: {
name: string;
createdBy: string;
boardId: number;
index: number;
importId?: number;
},
) => {
const { data } = await db
.from("list")
.insert({
publicId: generateUID(),
name: listInput.name,
createdBy: listInput.createdBy,
boardId: listInput.boardId,
index: listInput.index,
importId: listInput.importId,
})
.select(
`
id,
publicId,
name
`,
)
.limit(1)
.single();
return data;
};
export const getByPublicId = async (
db: SupabaseClient<Database>,
listPublicId: string,
) => {
const { data } = await db
.from("list")
.select(`id, boardId, index`)
.eq("publicId", listPublicId)
.limit(1)
.single();
return data;
};
export const getWithCardsByPublicId = async (
db: SupabaseClient<Database>,
listPublicId: string,
) => {
const { data } = await db
.from("list")
.select(`id, cards:card (index)`)
.eq("publicId", listPublicId)
.is("deletedAt", null)
.is("card.deletedAt", null)
.order("index", { foreignTable: "card", ascending: false })
.limit(1)
.single();
return data;
};
export const update = async (
db: SupabaseClient<Database>,
listInput: {
name: string;
},
args: {
listPublicId: string;
},
) => {
const { data } = await db
.from("list")
.update({ name: listInput.name })
.eq("publicId", args.listPublicId)
.is("deletedAt", null)
.select(`publicId, name`);
return data;
};
export const destroyAllByBoardId = async (
db: SupabaseClient<Database>,
args: {
boardId: number;
deletedAt: string;
deletedBy: string;
},
) => {
const result = await db
.from("list")
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.eq("boardId", args.boardId)
.is("deletedAt", null);
return result;
};
export const destroyById = async (
db: SupabaseClient<Database>,
args: {
listId: number;
deletedAt: string;
deletedBy: string;
},
) => {
const result = await db
.from("list")
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.eq("id", args.listId)
.is("deletedAt", null);
return result;
};
export const reorder = async (
db: SupabaseClient<Database>,
args: {
boardPublicId: number;
listPublicId: number;
currentIndex: number;
newIndex: number;
},
) => {
const { data } = await db.rpc("reorder_lists", {
board_id: args.boardPublicId,
list_id: args.listPublicId,
current_index: args.currentIndex,
new_index: args.newIndex,
});
return data;
};
export const shiftIndex = async (
db: SupabaseClient<Database>,
args: {
boardId: number;
listIndex: number;
},
) => {
const { data } = await db.rpc("shift_list_index", {
board_id: args.boardId,
list_index: args.listIndex,
});
return data;
};

View File

@@ -0,0 +1,13 @@
import { type Database } from "~/types/database.types";
import { type SupabaseClient } from "@supabase/supabase-js";
export const getById = async (db: SupabaseClient<Database>, userId: string) => {
const { data } = await db
.from("user")
.select(`id, name, email`)
.eq("id", userId)
.limit(1)
.single();
return data;
};

View File

@@ -0,0 +1,129 @@
import { generateUID } from "~/utils/generateUID";
import { type Database } from "~/types/database.types";
import { type SupabaseClient } from "@supabase/supabase-js";
export const create = async (
db: SupabaseClient<Database>,
workspaceInput: {
name: string;
slug: string;
createdBy: string;
},
) => {
const { data } = await db
.from("workspace")
.insert({
publicId: generateUID(),
name: workspaceInput.name,
slug: workspaceInput.name.toLowerCase(),
createdBy: workspaceInput.createdBy,
})
.select(`id, publicId, name`)
.limit(1)
.single();
if (data)
await db.from("workspace_members").insert({
publicId: generateUID(),
userId: workspaceInput.createdBy,
workspaceId: data.id,
createdBy: workspaceInput.createdBy,
role: "admin",
});
const newWorkspace = { ...data };
delete newWorkspace.id;
return newWorkspace;
};
export const getByPublicId = async (
db: SupabaseClient<Database>,
workspacePublicId: string,
) => {
const { data } = await db
.from("workspace")
.select(`id, publicId, name`)
.is("deletedAt", null)
.eq("publicId", workspacePublicId)
.limit(1)
.single();
return data;
};
export const getByPublicIdWithMembers = async (
db: SupabaseClient<Database>,
workspacePublicId: string,
) => {
const { data } = await db
.from("workspace")
.select(
`
publicId,
members: workspace_members (
publicId,
role,
user (
id,
name,
email
)
)
`,
)
.eq("publicId", workspacePublicId)
.is("deletedAt", null)
.limit(1)
.single();
return data;
};
export const getAllByUserId = async (
db: SupabaseClient<Database>,
userId: string,
) => {
const { data } = await db
.from("workspace_members")
.select(
`
role,
workspace (
publicId,
name
)
`,
)
.eq("userId", userId)
.is("deletedAt", null);
return data;
};
export const getMemberByPublicId = async (
db: SupabaseClient<Database>,
memberPublicId: string,
) => {
const { data } = await db
.from("workspace_members")
.select(`id`)
.eq("publicId", memberPublicId)
.limit(1)
.single();
return data;
};
export const getAllMembersByPublicIds = async (
db: SupabaseClient<Database>,
memberPublicIds: string[],
) => {
const { data } = await db
.from("workspace_members")
.select(`id`)
.eq("publicId", memberPublicIds);
return data;
};