chore: lint all router files

This commit is contained in:
Henry
2024-04-22 22:44:35 +01:00
parent 4e1e99cbac
commit 227e76524b
10 changed files with 537 additions and 558 deletions

View File

@@ -1,3 +1,6 @@
{ {
"spellright.addToSystemDictionary": true "spellright.addToSystemDictionary": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
} }

View File

@@ -1,9 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
export const authRouter = createTRPCRouter({ export const authRouter = createTRPCRouter({
login: publicProcedure login: publicProcedure
@@ -12,10 +9,10 @@ export const authRouter = createTRPCRouter({
const { data } = await ctx.db.auth.signInWithOtp({ const { data } = await ctx.db.auth.signInWithOtp({
email: input.email, email: input.email,
options: { options: {
emailRedirectTo: '/boards', emailRedirectTo: "/boards",
}, },
}) });
return data; return data;
}) }),
}); });

View File

@@ -1,98 +1,94 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import { import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
export const boardRouter = createTRPCRouter({ export const boardRouter = createTRPCRouter({
all: publicProcedure 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 ctx.db
.from('workspace') .from("workspace")
.select(`id`) .select(`id`)
.eq('publicId', input.workspacePublicId) .eq("publicId", input.workspacePublicId)
.limit(1) .limit(1)
.single(); .single();
if (!workspace.data) return; if (!workspace.data) return;
const { data } = await ctx.db const { data } = await ctx.db
.from('board') .from("board")
.select(` .select(`publicId, name`)
publicId, .is("deletedAt", null)
name .eq("workspaceId", workspace.data.id);
`)
.is('deletedAt', null)
.eq('workspaceId', workspace.data.id);
return data; return data;
}), }),
byId: publicProcedure 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 { data } = await ctx.db
.from('board') .from("board")
.select(` .select(
publicId, `
name,
workspace (
publicId,
members:workspace_members (
publicId,
user (
name
)
)
),
labels:label (
publicId, publicId,
name, name,
colourCode workspace (
),
lists:list (
publicId,
name,
boardId,
index,
cards:card (
publicId, publicId,
title,
description,
listId,
index,
labels:label (
publicId,
name,
colourCode
),
members:workspace_members ( members:workspace_members (
publicId, publicId,
user ( user (
name 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) .eq("publicId", input.id)
.is('deletedAt', null) .is("deletedAt", null)
.is('lists.deletedAt', null) .is("lists.deletedAt", null)
.is('lists.cards.deletedAt', null) .is("lists.cards.deletedAt", null)
.order('index', { foreignTable: 'list', ascending: true }) .order("index", { foreignTable: "list", ascending: true })
.order('index', { foreignTable: 'list.card', ascending: true }) .order("index", { foreignTable: "list.card", ascending: true })
.limit(1) .limit(1)
.single() .single();
return data; return data;
}), }),
create: publicProcedure create: protectedProcedure
.input( .input(
z.object({ z.object({
name: z.string().min(1), name: z.string().min(1),
workspacePublicId: z.string().min(12) workspacePublicId: z.string().min(12),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
@@ -101,91 +97,81 @@ export const boardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const workspace = await ctx.db const workspace = await ctx.db
.from('workspace') .from("workspace")
.select(`id`) .select(`id`)
.eq('publicId', input.workspacePublicId) .eq("publicId", input.workspacePublicId)
.limit(1) .limit(1)
.single(); .single();
if (!workspace.data) return; if (!workspace.data) return;
const { data } = await ctx.db const { data } = await ctx.db
.from('board') .from("board")
.insert({ .insert({
publicId: generateUID(), publicId: generateUID(),
name: input.name, name: input.name,
createdBy: userId, createdBy: userId,
workspaceId: workspace.data.id workspaceId: workspace.data.id,
}) })
.select(` .select(`publicId, name`);
publicId,
name
`);
return data; return data;
}), }),
update: publicProcedure update: protectedProcedure
.input( .input(
z.object({ z.object({
boardId: z.string().min(12), boardId: z.string().min(12),
name: z.string().min(1), name: z.string().min(1),
}))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
const { data } = await ctx.db
.from('board')
.update({ name: input.name })
.eq('publicId', input.boardId);
return data;
}), }),
delete: publicProcedure )
.input( .mutation(async ({ ctx, input }) => {
z.object({ const { data } = await ctx.db
boardPublicId: z.string().min(12), .from("board")
})) .update({ name: input.name })
.mutation(async ({ ctx, input }) => { .eq("publicId", input.boardId);
const userId = ctx.user?.id;
if (!userId) return; return data;
}),
delete: protectedProcedure
.input(
z.object({
boardPublicId: z.string().min(12),
}),
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
const board = await ctx.db const board = await ctx.db
.from('board') .from("board")
.select(` .select(`id, lists:list (id)`)
id, .eq("publicId", input.boardPublicId)
lists:list (id) .limit(1)
`) .single();
.eq('publicId', input.boardPublicId)
.limit(1)
.single();
if (!board.data) return; if (!board.data) return;
const listIds = board.data.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
.from("board")
.update({ deletedAt, deletedBy: userId })
.eq("id", board.data.id)
.is("deletedAt", null);
if (listIds.length) {
await ctx.db
.from("list")
.update({ deletedAt, deletedBy: userId })
.eq("boardId", board.data.id)
.is("deletedAt", null);
await ctx.db await ctx.db
.from('board') .from("card")
.update({ deletedAt, deletedBy: userId }) .update({ deletedAt, deletedBy: userId })
.eq('id', board.data.id) .in("listId", listIds)
.is('deletedAt', null); .is("deletedAt", null);
}
if (listIds.length) { }),
await ctx.db
.from('list')
.update({ deletedAt, deletedBy: userId })
.eq('boardId', board.data.id)
.is('deletedAt', null);
await ctx.db
.from('card')
.update({ deletedAt, deletedBy: userId })
.in('listId', listIds)
.is('deletedAt', null);
}
})
}); });

View File

@@ -1,19 +1,16 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import { import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
export const cardRouter = createTRPCRouter({ export const cardRouter = createTRPCRouter({
create: publicProcedure 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)), labelsPublicIds: z.array(z.string().min(12)),
memberPublicIds: z.array(z.string().min(12)) memberPublicIds: z.array(z.string().min(12)),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
@@ -22,25 +19,25 @@ export const cardRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const list = await ctx.db const list = await ctx.db
.from('list') .from("list")
.select(`id, cards:card (index)`) .select(`id, cards:card (index)`)
.eq('publicId', input.listPublicId) .eq("publicId", input.listPublicId)
.order('index', { foreignTable: 'card', ascending: false }) .order("index", { foreignTable: "card", ascending: false })
.limit(1) .limit(1)
.single(); .single();
if (!list.data?.id) return; if (!list.data?.id) return;
const latestCard = list.data.cards.length && list.data.cards[0] const latestCard = list.data.cards.length && list.data.cards[0];
const newCard = await ctx.db const newCard = await ctx.db
.from('card') .from("card")
.insert({ .insert({
publicId: generateUID(), publicId: generateUID(),
title: input.title, title: input.title,
createdBy: userId, createdBy: userId,
listId: list.data.id, listId: list.data.id,
index: latestCard ? latestCard.index + 1 : 0 index: latestCard ? latestCard.index + 1 : 0,
}) })
.select(`id`) .select(`id`)
.limit(1) .limit(1)
@@ -49,153 +46,152 @@ export const cardRouter = createTRPCRouter({
const newCardId = newCard.data?.id; const newCardId = newCard.data?.id;
if (newCardId && input.labelsPublicIds.length) { if (newCardId && input.labelsPublicIds.length) {
const labels = await ctx.db const labels = await ctx.db
.from('label') .from("label")
.select(`id`) .select(`id`)
.eq('publicId', input.labelsPublicIds); .eq("publicId", input.labelsPublicIds);
if (!labels.data?.length) return; if (!labels.data?.length) return;
const labelsInsert = labels.data.map((label) => ({ cardId: newCardId, labelId: label.id })); const labelsInsert = labels.data.map((label) => ({
cardId: newCardId,
labelId: label.id,
}));
await ctx.db await ctx.db.from("_card_labels").insert(labelsInsert);
.from('_card_labels')
.insert(labelsInsert);
} }
if (newCardId && input.memberPublicIds.length) { if (newCardId && input.memberPublicIds.length) {
const members = await ctx.db const members = await ctx.db
.from('workspace_members') .from("workspace_members")
.select(`id`) .select(`id`)
.eq('publicId', input.memberPublicIds); .eq("publicId", input.memberPublicIds);
if (!members.data?.length) return; if (!members.data?.length) return;
const membersInsert = members.data.map((member) => ({ cardId: newCardId, workspaceMemberId: member.id })); const membersInsert = members.data.map((member) => ({
cardId: newCardId,
workspaceMemberId: member.id,
}));
await ctx.db await ctx.db.from("_card_workspace_members").insert(membersInsert);
.from('_card_workspace_members')
.insert(membersInsert);
} }
return newCard; return newCard;
}), }),
addOrRemoveLabel: publicProcedure addOrRemoveLabel: protectedProcedure
.input( .input(
z.object({ z.object({
cardPublicId: z.string().min(12), cardPublicId: z.string().min(12),
labelPublicId: z.string().min(12), labelPublicId: z.string().min(12),
}),
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
const card = await ctx.db
.from('card')
.select(`id`)
.eq('publicId', input.cardPublicId)
.limit(1)
.single();
const label = await ctx.db
.from('label')
.select(`id`)
.eq('publicId', input.labelPublicId)
.limit(1)
.single();
if (!card.data || !label.data) return;
const existingLabel = await ctx.db
.from('_card_labels')
.select()
.eq('cardId', card.data.id)
.eq('labelId', label.data.id)
.limit(1)
.single();
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: publicProcedure )
.input( .mutation(async ({ ctx, input }) => {
z.object({ const userId = ctx.user?.id;
cardPublicId: z.string().min(12),
workspaceMemberPublicId: z.string().min(12),
}),
)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await ctx.db
.from('card') .from("card")
.select(`id`) .select(`id`)
.eq('publicId', input.cardPublicId) .eq("publicId", input.cardPublicId)
.limit(1) .limit(1)
.single(); .single();
const member = await ctx.db const label = await ctx.db
.from('workspace_members') .from("label")
.select(`id`) .select(`id`)
.eq('publicId', input.workspaceMemberPublicId) .eq("publicId", input.labelPublicId)
.limit(1) .limit(1)
.single(); .single();
if (!card.data || !member.data) return; if (!card.data || !label.data) return;
const existingMember = await ctx.db const existingLabel = await ctx.db
.from('_card_workspace_members') .from("_card_labels")
.select() .select()
.eq('cardId', card.data.id) .eq("cardId", card.data.id)
.eq('workspaceMemberId', member.data.id) .eq("labelId", label.data.id)
.limit(1) .limit(1)
.single(); .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 };
}
if (existingLabel.data) {
await ctx.db await ctx.db
.from('_card_workspace_members') .from("_card_labels")
.insert({ .delete()
cardId: card.data.id, .eq("cardId", card.data.id)
workspaceMemberId: member.data.id, .eq("labelId", label.data.id);
});
return { newMember: true }; 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) })) .mutation(async ({ ctx, input }) => {
.query(async ({ ctx, input }) => { const userId = ctx.user?.id;
const { data } = await ctx.db
.from('card') if (!userId) return;
.select(`
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, publicId,
title, title,
description, description,
@@ -238,127 +234,131 @@ export const cardRouter = createTRPCRouter({
name name
) )
) )
`) `,
.eq('publicId', input.id) )
.is('deletedAt', null) .eq("publicId", input.id)
.is('list.board.lists.deletedAt', null) .is("deletedAt", null)
.limit(1) .is("list.board.lists.deletedAt", null)
.single(); .limit(1)
.single();
return data; return data;
}),
update: protectedProcedure
.input(
z.object({
cardId: z.string().min(12),
title: z.string().min(1),
description: z.string(),
}), }),
update: publicProcedure )
.input( .mutation(async ({ ctx, input }) => {
z.object({ const userId = ctx.user?.id;
cardId: z.string().min(12),
title: z.string().min(1),
description: z.string(),
}))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
const { data } = await ctx.db const { data } = await ctx.db
.from('card') .from("card")
.update({ title: input.title, description: input.description }) .update({ title: input.title, description: input.description })
.eq('publicId', input.cardId) .eq("publicId", input.cardId)
.is('deletedAt', null); .is("deletedAt", null);
return data; return data;
}),
delete: protectedProcedure
.input(
z.object({
cardPublicId: z.string().min(12),
}), }),
delete: publicProcedure )
.input( .mutation(async ({ ctx, input }) => {
z.object({ const userId = ctx.user?.id;
cardPublicId: z.string().min(12),
}))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await ctx.db
.from('card') .from("card")
.select(`id, index, listId`) .select(`id, index, listId`)
.eq('publicId', input.cardPublicId) .eq("publicId", input.cardPublicId)
.limit(1) .limit(1)
.single(); .single();
if (!card.data) return; if (!card.data) return;
const deletedAt = new Date().toISOString(); const deletedAt = new Date().toISOString();
await ctx.db await ctx.db
.from('card') .from("card")
.update({ deletedAt, deletedBy: userId}) .update({ deletedAt, deletedBy: userId })
.eq('publicId', input.cardPublicId); .eq("publicId", input.cardPublicId);
await ctx.db await ctx.db
.from('card') .from("card")
.update({ deletedAt, deletedBy: userId}) .update({ deletedAt, deletedBy: userId })
.eq('publicId', input.cardPublicId); .eq("publicId", input.cardPublicId);
await ctx.db await ctx.db.rpc("shift_card_index", {
.rpc('shift_card_index', { list_id: card.data.listId,
list_id: card.data.listId, card_index: card.data.index,
card_index: card.data.index, });
}); }),
reorder: protectedProcedure
.input(
z.object({
cardId: z.string().min(12),
newListId: z.string().min(12),
newIndex: z.number().optional(),
}), }),
reorder: publicProcedure )
.input( .mutation(async ({ ctx, input }) => {
z.object({ const userId = ctx.user?.id;
cardId: z.string().min(12),
newListId: z.string().min(12),
newIndex: z.number().optional(),
}))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await ctx.db
.from('card') .from("card")
.select(`id, index, list (id)`) .select(`id, index, list (id)`)
.eq('publicId', input.cardId) .eq("publicId", input.cardId)
.is('deletedAt', null) .is("deletedAt", null)
.limit(1) .limit(1)
.single(); .single();
if (!card.data) return; if (!card.data) return;
const currentList = card.data.list; const currentList = card.data.list;
const currentIndex = card.data.index; const currentIndex = card.data.index;
let newIndex = input.newIndex; let newIndex = input.newIndex;
const newList = await ctx.db const newList = await ctx.db
.from('list') .from("list")
.select(`id, cards:card (index)`) .select(`id, cards:card (index)`)
.eq('publicId', input.newListId) .eq("publicId", input.newListId)
.is('deletedAt', null) .is("deletedAt", null)
.order('index', { foreignTable: 'card', ascending: false }) .order("index", { foreignTable: "card", ascending: false })
.limit(1) .limit(1)
.single(); .single();
if (!newList.data) return; if (!newList.data) return;
if (newIndex === undefined) { if (newIndex === undefined) {
const lastCardIndex = newList.data.cards.length ? newList.data.cards[0]?.index : undefined; const lastCardIndex = newList.data.cards.length
? newList.data.cards[0]?.index
: undefined;
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0; newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
} }
if (!currentList?.id || !newList?.data.id) return; if (!currentList?.id || !newList?.data.id) return;
const { error } = await ctx.db const { error } = await ctx.db.rpc("reorder_cards", {
.rpc('reorder_cards', { current_list_id: currentList.id,
current_list_id: currentList.id, new_list_id: newList.data.id,
new_list_id: newList.data.id, current_index: currentIndex,
current_index: currentIndex, new_index: newIndex,
new_index: newIndex, card_id: card.data.id,
card_id: card.data.id, });
});
return { success: !!error } return { success: !!error };
}) }),
}); });

View File

@@ -1,13 +1,10 @@
import { z } from "zod"; import { z } from "zod";
import { import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
const TRELLO_API_URL = 'https://api.trello.com/1'; const TRELLO_API_URL = "https://api.trello.com/1";
interface TrelloBoard { interface TrelloBoard {
id: string; id: string;
@@ -34,28 +31,28 @@ interface MemberData {
export const importRouter = createTRPCRouter({ export const importRouter = createTRPCRouter({
trello: createTRPCRouter({ trello: createTRPCRouter({
getBoards: publicProcedure getBoards: protectedProcedure
.input( .input(
z.object({ z.object({
apiKey: z.string().length(32), apiKey: z.string().length(32),
token: z.string().length(76), token: z.string().length(76),
}), }),
) )
.query(async ({ ctx, input }) => { .query(async ({ input }) => {
const userId = ctx.user?.id; const fetchMemberRes = await fetch(
`${TRELLO_API_URL}/tokens/${input.token}/member?key=${input.apiKey}`,
);
if (!userId) return; const member = (await fetchMemberRes.json()) as MemberData;
const fetchMemberRes = await fetch(`${TRELLO_API_URL}/tokens/${input.token}/member?key=${input.apiKey}`)
const member = await fetchMemberRes.json() as MemberData;
const boardIds = member.idBoards; const boardIds = member.idBoards;
const fetchBoard = async (boardId: string) => { const fetchBoard = async (boardId: string) => {
try { try {
const response = await fetch(`${TRELLO_API_URL}/boards/${boardId}?key=${input.apiKey}&token=${input.token}`); const response = await fetch(
const data = await response.json() as TrelloBoard; `${TRELLO_API_URL}/boards/${boardId}?key=${input.apiKey}&token=${input.token}`,
);
const data = (await response.json()) as TrelloBoard;
return data; return data;
} catch (error) { } catch (error) {
@@ -71,15 +68,18 @@ export const importRouter = createTRPCRouter({
const boardDataArray = await Promise.all(boards); const boardDataArray = await Promise.all(boards);
return boardDataArray.map((board) => ({ id: board.id, name: board.name })); return boardDataArray.map((board) => ({
id: board.id,
name: board.name,
}));
}), }),
importBoards: publicProcedure importBoards: protectedProcedure
.input( .input(
z.object({ z.object({
boardIds: z.array(z.string()), boardIds: z.array(z.string()),
apiKey: z.string().length(32), apiKey: z.string().length(32),
token: z.string().length(76), token: z.string().length(76),
workspacePublicId: z.string().min(12) workspacePublicId: z.string().min(12),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
@@ -88,12 +88,12 @@ export const importRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const newImport = await ctx.db const newImport = await ctx.db
.from('import') .from("import")
.insert({ .insert({
publicId: generateUID(), publicId: generateUID(),
source: 'trello', source: "trello",
createdBy: userId, createdBy: userId,
status: 'started' status: "started",
}) })
.select(`id`) .select(`id`)
.limit(1) .limit(1)
@@ -104,18 +104,20 @@ export const importRouter = createTRPCRouter({
let boardsCreated = 0; let boardsCreated = 0;
const workspace = await ctx.db const workspace = await ctx.db
.from('workspace') .from("workspace")
.select(`id`) .select(`id`)
.eq('publicId', input.workspacePublicId) .eq("publicId", input.workspacePublicId)
.is('deletedAt', null) .is("deletedAt", null)
.limit(1) .limit(1)
.single(); .single();
if (!workspace.data) return; if (!workspace.data) return;
for (const boardId of input.boardIds) { for (const boardId of input.boardIds) {
const response = await fetch(`${TRELLO_API_URL}/boards/${boardId}?key=${input.apiKey}&token=${input.token}&lists=open&cards=open`); const response = await fetch(
const data = await response.json() as TrelloBoard; `${TRELLO_API_URL}/boards/${boardId}?key=${input.apiKey}&token=${input.token}&lists=open&cards=open`,
);
const data = (await response.json()) as TrelloBoard;
const formattedData = { const formattedData = {
name: data.name, name: data.name,
@@ -125,25 +127,25 @@ export const importRouter = createTRPCRouter({
.filter((card) => card.idList === list.id) .filter((card) => card.idList === list.id)
.map((_card) => ({ .map((_card) => ({
name: _card.name, name: _card.name,
description: _card.desc description: _card.desc,
})) })),
})) })),
} };
const newBoard = await ctx.db const newBoard = await ctx.db
.from('board') .from("board")
.insert({ .insert({
publicId: generateUID(), publicId: generateUID(),
name: data.name, name: data.name,
createdBy: userId, createdBy: userId,
importId: newImportId, importId: newImportId,
workspaceId: workspace.data.id workspaceId: workspace.data.id,
}) })
.select(`id`) .select(`id`)
.limit(1) .limit(1)
.single(); .single();
const newBoardId = newBoard.data?.id const newBoardId = newBoard.data?.id;
if (!newBoardId) return; if (!newBoardId) return;
@@ -151,20 +153,20 @@ export const importRouter = createTRPCRouter({
for (const list of formattedData.lists) { for (const list of formattedData.lists) {
const newList = await ctx.db const newList = await ctx.db
.from('list') .from("list")
.insert({ .insert({
publicId: generateUID(), publicId: generateUID(),
name: list.name, name: list.name,
createdBy: userId, createdBy: userId,
boardId: newBoardId, boardId: newBoardId,
index: listIndex, index: listIndex,
importId: newImportId importId: newImportId,
}) })
.select(`id`) .select(`id`)
.limit(1) .limit(1)
.single(); .single();
const newListId = newList.data?.id const newListId = newList.data?.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) => ({
@@ -174,27 +176,25 @@ export const importRouter = createTRPCRouter({
createdBy: userId, createdBy: userId,
listId: newListId, listId: newListId,
index, index,
importId: newImportId importId: newImportId,
})) }));
await ctx.db await ctx.db.from("card").insert(cardsInsert);
.from('card')
.insert(cardsInsert);
} }
listIndex ++ listIndex++;
} }
boardsCreated ++ boardsCreated++;
} }
if (boardsCreated > 0 && newImportId) { if (boardsCreated > 0 && newImportId) {
await ctx.db await ctx.db
.from('import') .from("import")
.update({ status: 'success' }) .update({ status: "success" })
.eq('importId', newImportId); .eq("importId", newImportId);
} }
return { boardsCreated } return { boardsCreated };
}), }),
}) }),
}); });

View File

@@ -1,13 +1,10 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import { import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
export const labelRouter = createTRPCRouter({ export const labelRouter = createTRPCRouter({
create: publicProcedure create: protectedProcedure
.input( .input(
z.object({ z.object({
name: z.string().min(1).max(36), name: z.string().min(1).max(36),
@@ -21,17 +18,17 @@ export const labelRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const card = await ctx.db const card = await ctx.db
.from('card') .from("card")
.select(`id, list (boardId)`) .select(`id, list (boardId)`)
.eq('publicId', input.cardPublicId) .eq("publicId", input.cardPublicId)
.is('deletedAt', null) .is("deletedAt", null)
.limit(1) .limit(1)
.single(); .single();
if (!card.data?.list) return; if (!card.data?.list) return;
const newLabel = await ctx.db const newLabel = await ctx.db
.from('label') .from("label")
.insert({ .insert({
publicId: generateUID(), publicId: generateUID(),
name: input.name, name: input.name,
@@ -45,12 +42,10 @@ export const labelRouter = createTRPCRouter({
if (!newLabel.data) return; if (!newLabel.data) return;
await ctx.db await ctx.db.from("_card_labels").insert({
.from('_card_labels') cardId: card.data.id,
.insert({ labelId: newLabel.data.id,
cardId: card.data.id, });
labelId: newLabel.data.id,
})
return newLabel.data; return newLabel.data;
}), }),

View File

@@ -1,17 +1,14 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import { import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
export const listRouter = createTRPCRouter({ export const listRouter = createTRPCRouter({
create: publicProcedure create: protectedProcedure
.input( .input(
z.object({ z.object({
name: z.string().min(1), name: z.string().min(1),
boardPublicId: z.string().min(12) boardPublicId: z.string().min(12),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
@@ -20,80 +17,72 @@ export const listRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const board = await ctx.db const board = await ctx.db
.from('board') .from("board")
.select(`id, lists:list (index)`) .select(`id, lists:list (index)`)
.eq('publicId', input.boardPublicId) .eq("publicId", input.boardPublicId)
.order('index', { foreignTable: 'list', ascending: false }) .order("index", { foreignTable: "list", ascending: false })
.is('list.deletedAt', null) .is("list.deletedAt", null)
.limit(1) .limit(1)
.single(); .single();
if (!board.data) return; if (!board.data) return;
const latestListIndex = board.data.lists[0]?.index const latestListIndex = board.data.lists[0]?.index;
const { data } = await ctx.db const { data } = await ctx.db.from("list").insert({
.from('list') publicId: generateUID(),
.insert({ name: input.name,
publicId: generateUID(), createdBy: userId,
name: input.name, boardId: board.data.id,
createdBy: userId, index: latestListIndex ? latestListIndex + 1 : 0,
boardId: board.data.id, }).select(`
index: latestListIndex ? latestListIndex + 1 : 0
})
.select(`
publicId, publicId,
name name
`); `);
return data; return data;
}), }),
reorder: publicProcedure reorder: protectedProcedure
.input( .input(
z.object({ z.object({
boardId: z.string().min(12), boardId: z.string().min(12),
listId: z.string().min(12), listId: z.string().min(12),
currentIndex: z.number(), currentIndex: z.number(),
newIndex: z.number(), newIndex: z.number(),
})) }),
)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
const list = await ctx.db const list = await ctx.db
.from('list') .from("list")
.select(`id, boardId`) .select(`id, boardId`)
.eq('publicId', input.listId) .eq("publicId", input.listId)
.limit(1) .limit(1)
.single(); .single();
if (!list?.data) return; if (!list?.data) return;
const { data } = await ctx.db const { data } = await ctx.db.rpc("reorder_lists", {
.rpc('reorder_list', { board_id: list.data.boardId,
board_id: list.data.boardId, list_id: list.data.id,
list_id: list.data.id, current_index: input.currentIndex,
current_index: input.currentIndex, new_index: input.newIndex,
new_index: input.newIndex, });
})
return data; return data;
}), }),
delete: publicProcedure delete: protectedProcedure
.input( .input(
z.object({ z.object({
listPublicId: z.string().min(12), listPublicId: z.string().min(12),
})) }),
)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id; const userId = ctx.user?.id;
if (!userId) return;
const list = await ctx.db const list = await ctx.db
.from('list') .from("list")
.select(`id, boardId, index`) .select(`id, boardId, index`)
.eq('publicId', input.listPublicId) .eq("publicId", input.listPublicId)
.limit(1) .limit(1)
.single(); .single();
@@ -102,43 +91,39 @@ export const listRouter = createTRPCRouter({
const deletedAt = new Date().toISOString(); const deletedAt = new Date().toISOString();
await ctx.db await ctx.db
.from('list') .from("list")
.update({ deletedAt, deletedBy: userId }) .update({ deletedAt, deletedBy: userId })
.eq('id', list.data.id) .eq("id", list.data.id)
.is('deletedAt', null); .is("deletedAt", null);
await ctx.db await ctx.db
.from('card') .from("card")
.update({ deletedAt, deletedBy: userId }) .update({ deletedAt, deletedBy: userId })
.eq('listId', list.data.id) .eq("listId", list.data.id)
.is('deletedAt', null); .is("deletedAt", null);
const { data } = await ctx.db const { data } = await ctx.db.rpc("shift_list_index", {
.rpc('shift_list_index', { board_id: list.data.boardId,
board_id: list.data.boardId, list_index: list.data.index,
list_index: list.data.index });
})
return data; return data;
}), }),
update: publicProcedure update: protectedProcedure
.input( .input(
z.object({ z.object({
listPublicId: z.string().min(12), listPublicId: z.string().min(12),
name: z.string().min(1), name: z.string().min(1),
})) }),
)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
const { data } = await ctx.db const { data } = await ctx.db
.from('list') .from("list")
.update({ name: input.name }) .update({ name: input.name })
.eq('publicId', input.listPublicId) .eq("publicId", input.listPublicId)
.is('deletedAt', null) .is("deletedAt", null)
.select(`publicId, name`); .select(`publicId, name`);
return data; return data;
}), }),
}); });

View File

@@ -1,61 +1,57 @@
import { z } from "zod"; import { z } from "zod";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import { import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
createTRPCRouter,
publicProcedure,
} from "~/server/api/trpc";
export const workspaceRouter = createTRPCRouter({ export const workspaceRouter = createTRPCRouter({
all: publicProcedure all: protectedProcedure.query(async ({ ctx }) => {
.query(async ({ ctx }) => { const userId = ctx.user?.id;
const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
const { data } = await ctx.db const { data } = await ctx.db
.from('workspace_members') .from("workspace_members")
.select(` .select(
`
role, role,
workspace ( workspace (
publicId, publicId,
name name
) )
`) `,
.eq('userId', userId) )
.is('deletedAt', null); .eq("userId", userId)
.is("deletedAt", null);
return data; return data;
}), }),
byId: publicProcedure 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 userId = ctx.user?.id;
if (!userId) return;
const { data } = await ctx.db const { data } = await ctx.db
.from('workspace') .from("workspace")
.select(` .select(
publicId, `
members: workspace_members (
publicId, publicId,
role, members: workspace_members (
user ( publicId,
id, role,
name, user (
email id,
name,
email
)
) )
) `,
`) )
.eq('publicId', input.publicId) .eq("publicId", input.publicId)
.is('deletedAt', null) .is("deletedAt", null)
.limit(1) .limit(1)
.single(); .single();
return data; return data;
}), }),
create: publicProcedure create: protectedProcedure
.input( .input(
z.object({ z.object({
name: z.string().min(1), name: z.string().min(1),
@@ -67,34 +63,28 @@ export const workspaceRouter = createTRPCRouter({
if (!userId) return; if (!userId) return;
const workspace = await ctx.db const workspace = await ctx.db
.from('workspace') .from("workspace")
.insert({ .insert({
publicId: generateUID(), publicId: generateUID(),
name: input.name, name: input.name,
slug: input.name.toLowerCase(), slug: input.name.toLowerCase(),
createdBy: userId, createdBy: userId,
}) })
.select(` .select(`id, publicId, name`)
id,
publicId,
name
`)
.limit(1) .limit(1)
.single() .single();
const newWorkspaceId = workspace.data?.id const newWorkspaceId = workspace.data?.id;
if (!newWorkspaceId) return; if (!newWorkspaceId) return;
await ctx.db await ctx.db.from("workspace_members").insert({
.from('workspace_members') publicId: generateUID(),
.insert({ userId,
publicId: generateUID(), workspaceId: newWorkspaceId,
userId, createdBy: userId,
workspaceId: newWorkspaceId, role: "admin",
createdBy: userId, });
role: 'admin'
})
const newWorkspace = { ...workspace.data }; const newWorkspace = { ...workspace.data };

View File

@@ -6,14 +6,14 @@
* TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
* need to use are documented accordingly near the end. * need to use are documented accordingly near the end.
*/ */
import { initTRPC } from "@trpc/server"; import { initTRPC, TRPCError } from "@trpc/server";
import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import superjson from "superjson"; import superjson from "superjson";
import { ZodError } from "zod"; import { ZodError } from "zod";
import createClient from "~/utils/supabase/api"; import createClient from "~/utils/supabase/api";
import { type Database } from "~/types/database.types"; import { type Database } from "~/types/database.types";
import { type SupabaseClient } from '@supabase/supabase-js'; import { type SupabaseClient } from "@supabase/supabase-js";
/** /**
* 1. CONTEXT * 1. CONTEXT
@@ -29,7 +29,7 @@ type User = {
interface CreateContextOptions { interface CreateContextOptions {
user: User | null; user: User | null;
db: SupabaseClient<Database> db: SupabaseClient<Database>;
} }
/** /**
@@ -59,9 +59,11 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
export const createTRPCContext = async (_opts: CreateNextContextOptions) => { export const createTRPCContext = async (_opts: CreateNextContextOptions) => {
const db = createClient(_opts.req, _opts.res); const db = createClient(_opts.req, _opts.res);
const { data: { user } } = await db.auth.getUser() const {
data: { user },
} = await db.auth.getUser();
return createInnerTRPCContext({ user, db }); return createInnerTRPCContext({ db, user });
}; };
/** /**
@@ -115,3 +117,24 @@ export const createTRPCRouter = t.router;
* are logged in. * are logged in.
*/ */
export const publicProcedure = t.procedure; export const publicProcedure = t.procedure;
/** Reusable middleware that enforces users are logged in before running the procedure. */
const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx,
});
});
/**
* Protected (authenticated) procedure
*
* If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
* the session is valid and guarantees `ctx.session.user` is not null.
*
* @see https://trpc.io/docs/procedures
*/
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);