feat: convert board router to supabase

This commit is contained in:
Henry
2024-04-13 16:23:47 +01:00
parent e048e243d4
commit 1712a1a41b
7 changed files with 1336 additions and 99 deletions

View File

@@ -1,7 +1,4 @@
import { z } from "zod"; import { z } from "zod";
import { and, eq, asc, isNull, inArray } from "drizzle-orm";
import { boards, cards, lists, workspaces } from "~/server/db/schema";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
import { import {
@@ -13,78 +10,81 @@ export const boardRouter = createTRPCRouter({
all: publicProcedure all: publicProcedure
.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 userId = ctx.session?.user.id; const workspace = await ctx.supabase
.from('workspace')
.select(`id`)
.eq('publicId', input.workspacePublicId)
.limit(1)
.single();
// @todo: validate user has access to workspace if (!workspace.data) return;
// if (!userId) return const { data } = await ctx.supabase
.from('board')
// const workspace = await ctx.db.query.workspaces.findFirst({ .select(`
// where: eq(workspaces.publicId, input.workspacePublicId), publicId,
// }) name
`)
// if (!workspace) return; .is('deletedAt', null)
.eq('workspaceId', workspace.data.id);
const { data, error } = await ctx.supabase.from('board').select(`
publicId,
name
`).is('deletedAt', null);
return data; return data;
}), }),
byId: publicProcedure byId: publicProcedure
.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, error } = await ctx.supabase.from('board').select(` const { data } = await ctx.supabase
publicId, .from('board')
name, .select(`
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;
}), }),
@@ -96,22 +96,35 @@ export const boardRouter = createTRPCRouter({
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.session?.user.id; const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
const workspace = await ctx.db.query.workspaces.findFirst({ const workspace = await ctx.supabase
where: eq(workspaces.publicId, input.workspacePublicId), .from('workspace')
}) .select(`id`)
.eq('publicId', input.workspacePublicId)
.limit(1)
.single();
if (!workspace) return; console.log({ workspace })
return ctx.db.insert(boards).values({ if (!workspace.data) return;
publicId: generateUID(),
name: input.name, const { data, error } = await ctx.supabase
createdBy: userId, .from('board')
workspaceId: workspace.id .insert({
}); publicId: generateUID(),
name: input.name,
createdBy: userId,
workspaceId: workspace.data.id
})
.select(`
publicId,
name
`);
return data;
}), }),
update: publicProcedure update: publicProcedure
.input( .input(
@@ -119,41 +132,59 @@ export const boardRouter = createTRPCRouter({
boardId: z.string().min(12), boardId: z.string().min(12),
name: z.string().min(1), name: z.string().min(1),
})) }))
.mutation(({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.session?.user.id; const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
return ctx.db.update(boards).set({ name: input.name }).where(eq(boards.publicId, input.boardId)); const { data } = await ctx.supabase
.from('board')
.update({ name: input.name })
.eq('publicId', input.boardId);
return data;
}), }),
delete: publicProcedure delete: publicProcedure
.input( .input(
z.object({ z.object({
boardPublicId: z.string().min(12), boardPublicId: z.string().min(12),
})) }))
.mutation(({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.session?.user.id; const userId = ctx.user?.id;
if (!userId) return; if (!userId) return;
return ctx.db.transaction(async (tx) => {
const board = await tx.query.boards.findFirst({
where: eq(boards.publicId, input.boardPublicId),
with: {
lists: true,
}
})
if (!board) return;
const listIds = board.lists.map((list) => list.id) const board = await ctx.supabase
.from('board')
await tx.update(boards).set({ deletedAt: new Date(), deletedBy: userId }).where(eq(boards.id, board.id)); .select(`
id,
lists:list (id)
`)
.eq('publicId', input.boardPublicId)
.limit(1)
.single();
if (!board.data) return;
if (listIds.length) { const listIds = board.data.lists.map((list) => list.id);
await tx.update(lists).set({ deletedAt: new Date(), deletedBy: userId }).where(eq(lists.boardId, board.id));
await tx.update(cards).set({ deletedAt: new Date(), deletedBy: userId }).where(inArray(cards.listId, listIds)); const deletedAt = new Date().toString();
}
}) await ctx.supabase
}), .from('board')
.update({ deletedAt, deletedBy: userId })
.eq('id', board.data.id);
if (listIds.length) {
await ctx.supabase
.from('list')
.update({ deletedAt, deletedBy: userId })
.eq('boardId', board.data.id);
await ctx.supabase
.from('card')
.update({ deletedAt, deletedBy: userId })
.in('listId', listIds);
}
})
}); });

View File

@@ -1,11 +1,5 @@
import { sql } from '@vercel/postgres' import { sql } from '@vercel/postgres'
import { drizzle } from 'drizzle-orm/vercel-postgres' import { drizzle } from 'drizzle-orm/vercel-postgres'
import { createClient } from '@supabase/supabase-js'
import { type Database } from "~/types/database.types";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient<Database>(supabaseUrl!, supabaseKey!);
import * as schema from "./schema"; import * as schema from "./schema";

View File

@@ -0,0 +1 @@
ALTER TABLE "board" ALTER COLUMN "deletedAt" DROP DEFAULT;

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,13 @@
"when": 1711571659259, "when": 1711571659259,
"tag": "0000_legal_quicksilver", "tag": "0000_legal_quicksilver",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1713021051974,
"tag": "0001_stale_mattie_franklin",
"breakpoints": true
} }
] ]
} }

View File

@@ -27,7 +27,7 @@ export const boards = pgTable(
createdBy: uuid("createdBy").notNull().references(() => users.id), createdBy: uuid("createdBy").notNull().references(() => users.id),
createdAt: timestamp("createdAt").defaultNow().notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt"), updatedAt: timestamp("updatedAt"),
deletedAt: timestamp("deletedAt").defaultNow(), deletedAt: timestamp("deletedAt"),
deletedBy: uuid("deletedBy").references(() => users.id), deletedBy: uuid("deletedBy").references(() => users.id),
importId: bigint("importId", { mode: "number" }).references(() => imports.id), importId: bigint("importId", { mode: "number" }).references(() => imports.id),
workspaceId: bigint("workspaceId", { mode: "number" }).notNull().references(() => workspaces.id), workspaceId: bigint("workspaceId", { mode: "number" }).notNull().references(() => workspaces.id),

25
src/utils/supabase/api.ts Normal file
View File

@@ -0,0 +1,25 @@
import { createServerClient, type CookieOptions, serialize } from '@supabase/ssr'
import { type NextApiRequest, type NextApiResponse } from 'next'
import { type Database } from "~/types/database.types";
export default function createClient(req: NextApiRequest, res: NextApiResponse) {
const supabase = createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return req.cookies[name]
},
set(name: string, value: string, options: CookieOptions) {
res.appendHeader('Set-Cookie', serialize(name, value, options))
},
remove(name: string, options: CookieOptions) {
res.appendHeader('Set-Cookie', serialize(name, '', options))
},
},
}
)
return supabase
}