feat: switch next auth and drizzle to supabase (#1)

* feat: add supabase

* feat: setup auth

* feat: test refactoring board queries

* feat: convert to pages router

* feat: convert board router to supabase

* feat: swap out drizzle for supabase in label, list and workspace routers

* feat: swap out drizzle for supabase on the import router

* feat: swap drizzle for supabase on create new card

* feat: switch card router to use supabase

* feat: finish swapping drizzle for supabase

* chore: lint all router files

* fix: signout

* chore: fix types
This commit is contained in:
Henry
2024-04-23 11:57:34 +01:00
committed by GitHub
parent 1711df03e7
commit d15870118d
96 changed files with 3636 additions and 1521 deletions

View File

@@ -1,358 +1,364 @@
import { z } from "zod";
import { and, desc, eq, isNull, inArray, sql } from "drizzle-orm";
import { cards, cardsToLabels, cardToWorkspaceMembers, labels, lists, workspaceMembers } from "~/server/db/schema";
import { generateUID } from "~/utils/generateUID";
import {
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
export const cardRouter = createTRPCRouter({
create: publicProcedure
create: protectedProcedure
.input(
z.object({
title: z.string().min(1),
listPublicId: z.string().min(12),
labelsPublicIds: z.array(z.string().min(12)),
memberPublicIds: z.array(z.string().min(12))
memberPublicIds: z.array(z.string().min(12)),
}),
)
.mutation(({ ctx, input }) => {
const userId = ctx.session?.user.id;
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
return ctx.db.transaction(async (tx) => {
const list = await tx.query.lists.findFirst({
where: eq(lists.publicId, input.listPublicId),
columns: {
id: true
}
});
const list = await ctx.db
.from("list")
.select(`id, cards:card (index)`)
.eq("publicId", input.listPublicId)
.order("index", { foreignTable: "card", ascending: false })
.limit(1)
.single();
if (!list) return;
if (!list.data?.id) return;
const latestCard = await tx.query.cards.findFirst({
where: and(eq(cards.listId, list.id), isNull(cards.deletedAt)),
columns: {
index: true
},
orderBy: desc(cards.index)
});
const latestCard = list.data.cards.length && list.data.cards[0];
const newCard = await tx.insert(cards).values({
const newCard = await ctx.db
.from("card")
.insert({
publicId: generateUID(),
title: input.title,
createdBy: userId,
listId: list.id,
index: latestCard ? latestCard.index + 1 : 0
}).returning({ id: cards.id });
listId: list.data.id,
index: latestCard ? latestCard.index + 1 : 0,
})
.select(`id`)
.limit(1)
.single();
const newCardId = newCard[0]?.id;
const newCardId = newCard.data?.id;
if (newCardId && input.labelsPublicIds.length) {
const labels = await tx.query.labels.findMany({
where: inArray(cards.publicId, input.labelsPublicIds),
});
if (newCardId && input.labelsPublicIds.length) {
const labels = await ctx.db
.from("label")
.select(`id`)
.eq("publicId", input.labelsPublicIds);
if (!labels.length) return;
if (!labels.data?.length) return;
const labelsInsert = labels.map((label) => ({ cardId: newCardId, labelId: label.id }))
const labelsInsert = labels.data.map((label) => ({
cardId: newCardId,
labelId: label.id,
}));
await tx.insert(cardsToLabels).values(labelsInsert);
}
await ctx.db.from("_card_labels").insert(labelsInsert);
}
if (newCardId && input.memberPublicIds.length) {
const members = await tx.query.workspaceMembers.findMany({
where: inArray(workspaceMembers.publicId, input.memberPublicIds),
});
if (newCardId && input.memberPublicIds.length) {
const members = await ctx.db
.from("workspace_members")
.select(`id`)
.eq("publicId", input.memberPublicIds);
if (!members.length) return;
if (!members.data?.length) return;
const membersInsert = members.map((member) => ({ cardId: newCardId, workspaceMemberId: member.id}))
const membersInsert = members.data.map((member) => ({
cardId: newCardId,
workspaceMemberId: member.id,
}));
await tx.insert(cardToWorkspaceMembers).values(membersInsert);
}
await ctx.db.from("_card_workspace_members").insert(membersInsert);
}
return newCard;
})
return newCard;
}),
addOrRemoveLabel: publicProcedure
.input(
z.object({
cardPublicId: z.string().min(12),
labelPublicId: z.string().min(12),
}),
)
.mutation(({ ctx, input }) => {
const userId = ctx.session?.user.id;
if (!userId) return;
return ctx.db.transaction(async (tx) => {
const card = await tx.query.cards.findFirst({
where: and(eq(cards.publicId, input.cardPublicId), isNull(cards.deletedAt)),
});
const label = await tx.query.labels.findFirst({
where: eq(labels.publicId, input.labelPublicId),
});
if (!card || !label) return;
const labelExists = await tx.query.cardsToLabels.findFirst({
where: and(eq(cardsToLabels.cardId, card.id), eq(cardsToLabels.labelId, label.id)),
});
if (labelExists) {
return tx.delete(cardsToLabels).where(and(eq(cardsToLabels.cardId, card.id), eq(cardsToLabels.labelId, label.id)),);
}
return tx.insert(cardsToLabels).values({
cardId: card.id,
labelId: label.id,
});
})
addOrRemoveLabel: protectedProcedure
.input(
z.object({
cardPublicId: z.string().min(12),
labelPublicId: z.string().min(12),
}),
addOrRemoveMember: publicProcedure
.input(
z.object({
cardPublicId: z.string().min(12),
workspaceMemberPublicId: z.string().min(12),
}),
)
.mutation(({ ctx, input }) => {
const userId = ctx.session?.user.id;
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
if (!userId) return;
return ctx.db.transaction(async (tx) => {
const card = await tx.query.cards.findFirst({
where: and(eq(cards.publicId, input.cardPublicId), isNull(cards.deletedAt)),
});
const card = await ctx.db
.from("card")
.select(`id`)
.eq("publicId", input.cardPublicId)
.limit(1)
.single();
const member = await tx.query.workspaceMembers.findFirst({
where: eq(workspaceMembers.publicId, input.workspaceMemberPublicId),
});
if (!card || !member) return;
const label = await ctx.db
.from("label")
.select(`id`)
.eq("publicId", input.labelPublicId)
.limit(1)
.single();
const memberExists = await tx.query.cardToWorkspaceMembers.findFirst({
where: and(eq(cardToWorkspaceMembers.cardId, card.id), eq(cardToWorkspaceMembers.workspaceMemberId, member.id)),
});
if (!card.data || !label.data) return;
if (memberExists) {
return tx.delete(cardToWorkspaceMembers).where(and(eq(cardToWorkspaceMembers.cardId, card.id), eq(cardToWorkspaceMembers.workspaceMemberId, member.id)),);
}
const existingLabel = await ctx.db
.from("_card_labels")
.select()
.eq("cardId", card.data.id)
.eq("labelId", label.data.id)
.limit(1)
.single();
return tx.insert(cardToWorkspaceMembers).values({
cardId: card.id,
workspaceMemberId: member.id,
});
})
if (existingLabel.data) {
await ctx.db
.from("_card_labels")
.delete()
.eq("cardId", card.data.id)
.eq("labelId", label.data.id);
return { newLabel: false };
}
await ctx.db.from("_card_labels").insert({
cardId: card.data.id,
labelId: label.data.id,
});
return { newLabel: true };
}),
addOrRemoveMember: protectedProcedure
.input(
z.object({
cardPublicId: z.string().min(12),
workspaceMemberPublicId: z.string().min(12),
}),
byId: publicProcedure
.input(z.object({ id: z.string().min(12) }))
.query(({ ctx, input }) =>
ctx.db.query.cards.findFirst({
with: {
labels: {
columns: {
labelId: false,
cardId: false,
},
with: {
label: {
columns: {
publicId: true,
name: true,
colourCode: true,
}
},
}
},
list: {
columns: {
publicId: true,
},
with: {
board: {
columns: {
publicId: true,
name: true,
},
with: {
labels: {
columns: {
publicId: true,
colourCode: true,
name: true,
}
},
lists: {
columns: {
publicId: true,
name: true,
},
where: isNull(lists.deletedAt)
},
workspace: {
columns: {
publicId: true,
},
with: {
members: {
columns: {
publicId: true,
},
with: {
user: {
columns: {
id: true,
name: true,
}
}
}
}
}
}
},
}
}
},
members: {
columns: {
workspaceMemberId: false,
cardId: false,
},
with: {
member: {
columns: {
publicId: true,
},
with: {
user: {
columns: {
id: true,
name: true,
}
}
}
},
}
},
},
where: and(eq(cards.publicId, input.id), isNull(cards.deletedAt)),
})
),
update: publicProcedure
.input(
z.object({
cardId: z.string().min(12),
title: z.string().min(1),
description: z.string(),
}))
.mutation(({ ctx, input }) => {
const userId = ctx.session?.user.id;
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
if (!userId) return;
return ctx.db.update(cards).set({ title: input.title, description: input.description }).where(and(eq(cards.publicId, input.cardId), isNull(cards.deletedAt)));
const card = await ctx.db
.from("card")
.select(`id`)
.eq("publicId", input.cardPublicId)
.limit(1)
.single();
const member = await ctx.db
.from("workspace_members")
.select(`id`)
.eq("publicId", input.workspaceMemberPublicId)
.limit(1)
.single();
if (!card.data || !member.data) return;
const existingMember = await ctx.db
.from("_card_workspace_members")
.select()
.eq("cardId", card.data.id)
.eq("workspaceMemberId", member.data.id)
.limit(1)
.single();
if (existingMember.data) {
await ctx.db
.from("_card_workspace_members")
.delete()
.eq("cardId", card.data.id)
.eq("workspaceMemberId", member.data.id);
return { newMember: false };
}
await ctx.db.from("_card_workspace_members").insert({
cardId: card.data.id,
workspaceMemberId: member.data.id,
});
return { newMember: true };
}),
byId: protectedProcedure
.input(z.object({ id: z.string().min(12) }))
.query(async ({ ctx, input }) => {
const { data } = await ctx.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", input.id)
.is("deletedAt", null)
.is("list.board.lists.deletedAt", null)
.limit(1)
.single();
return data;
}),
update: protectedProcedure
.input(
z.object({
cardId: z.string().min(12),
title: z.string().min(1),
description: z.string(),
}),
delete: publicProcedure
.input(
z.object({
cardPublicId: z.string().min(12),
}))
.mutation(({ ctx, input }) => {
const userId = ctx.session?.user.id;
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
if (!userId) return;
return ctx.db.transaction(async (tx) => {
const card = await tx.query.cards.findFirst({
where: eq(cards.publicId, input.cardPublicId),
})
const { data } = await ctx.db
.from("card")
.update({ title: input.title, description: input.description })
.eq("publicId", input.cardId)
.is("deletedAt", null);
if (!card) return;
await tx.update(cards).set({ deletedAt: new Date(), deletedBy: userId}).where(eq(cards.publicId, input.cardPublicId));
await tx.execute(sql`UPDATE ${cards} SET ${cards.index} = ${cards.index} - 1 WHERE ${cards.listId} = ${card.listId} AND ${cards.index} > ${card.index} AND ${cards.deletedAt} IS NULL;`);
})
return data;
}),
delete: protectedProcedure
.input(
z.object({
cardPublicId: z.string().min(12),
}),
reorder: publicProcedure
.input(
z.object({
cardId: z.string().min(12),
newListId: z.string().min(12),
newIndex: z.number().optional(),
}))
.mutation(({ ctx, input }) => {
const userId = ctx.session?.user.id;
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
if (!userId) return;
return ctx.db.transaction(async (tx) => {
const card = await tx.query.cards.findFirst({
with: {
list: true,
},
where: and(eq(cards.publicId, input.cardId), isNull(cards.deletedAt)),
});
const card = await ctx.db
.from("card")
.select(`id, index, listId`)
.eq("publicId", input.cardPublicId)
.limit(1)
.single();
if (!card) return;
if (!card.data) return;
const currentList = card.list;
const currentIndex = card.index;
let newIndex = input.newIndex;
const deletedAt = new Date().toISOString();
const newList = await tx.query.lists.findFirst({
with: {
cards: {
orderBy: [desc(cards.index)],
limit: 1,
},
},
where: and(eq(lists.publicId, input.newListId), isNull(cards.deletedAt)),
});
await ctx.db
.from("card")
.update({ deletedAt, deletedBy: userId })
.eq("publicId", input.cardPublicId);
if (!newList) return;
await ctx.db
.from("card")
.update({ deletedAt, deletedBy: userId })
.eq("publicId", input.cardPublicId);
if (newIndex === undefined) {
const lastCardIndex = newList.cards.length ? newList.cards[0]?.index : undefined;
await ctx.db.rpc("shift_card_index", {
list_id: card.data.listId,
card_index: card.data.index,
});
}),
reorder: protectedProcedure
.input(
z.object({
cardId: z.string().min(12),
newListId: z.string().min(12),
newIndex: z.number().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
}
if (!userId) return;
if (!currentList?.id || !newList?.id) return;
const card = await ctx.db
.from("card")
.select(`id, index, list (id)`)
.eq("publicId", input.cardId)
.is("deletedAt", null)
.limit(1)
.single();
if (!card.data) return;
if (currentList.id === newList.id) {
await tx.execute(sql`
UPDATE ${cards}
SET index =
CASE
WHEN ${cards.index} = ${currentIndex} THEN ${newIndex}
WHEN ${currentIndex} < ${newIndex} AND ${cards.index} > ${currentIndex} AND ${cards.index} <= ${newIndex} THEN ${cards.index} - 1
WHEN ${currentIndex} > ${newIndex} AND ${cards.index} >= ${newIndex} AND ${cards.index} < ${currentIndex} THEN ${cards.index} + 1
ELSE ${cards.index}
END
WHERE ${cards.listId} = ${currentList.id} AND ${cards.deletedAt} IS NULL;
`);
} else {
await tx.execute(sql`UPDATE ${cards} SET index = index + 1 WHERE ${cards.listId} = ${newList.id} AND ${cards.index} >= ${newIndex} AND ${cards.deletedAt} IS NULL;`)
const currentList = card.data.list;
const currentIndex = card.data.index;
await tx.execute(sql`UPDATE ${cards} SET index = index - 1 WHERE ${cards.listId} = ${currentList.id} AND ${cards.index} >= ${currentIndex} AND ${cards.deletedAt} IS NULL;`)
let newIndex = input.newIndex;
await tx
.update(cards)
.set({ listId: newList.id, index: newIndex })
.where(and(eq(cards.publicId, input.cardId), isNull(cards.deletedAt)));
}
})
})
});
const newList = await ctx.db
.from("list")
.select(`id, cards:card (index)`)
.eq("publicId", input.newListId)
.is("deletedAt", null)
.order("index", { foreignTable: "card", ascending: false })
.limit(1)
.single();
if (!newList.data) return;
if (newIndex === undefined) {
const lastCardIndex = newList.data.cards.length
? newList.data.cards[0]?.index
: undefined;
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
}
if (!currentList?.id || !newList?.data.id) return;
const { error } = await ctx.db.rpc("reorder_cards", {
current_list_id: currentList.id,
new_list_id: newList.data.id,
current_index: currentIndex,
new_index: newIndex,
card_id: card.data.id,
});
return { success: !!error };
}),
});