refactor: reinstate drizzle
This commit is contained in:
@@ -57,18 +57,11 @@ export const cardRouter = createTRPCRouter({
|
||||
|
||||
let index = 0;
|
||||
|
||||
if (list.cards.length) {
|
||||
if (input.position === "end" && lastCard) index = lastCard.index + 1;
|
||||
|
||||
if (input.position === "start") {
|
||||
await cardRepo.pushIndex(ctx.db, {
|
||||
listId: list.id,
|
||||
cardIndex: 0,
|
||||
});
|
||||
}
|
||||
if (list.cards.length && input.position === "end" && lastCard) {
|
||||
index = lastCard.index + 1;
|
||||
}
|
||||
|
||||
const newCard = await cardRepo.create(ctx.db, {
|
||||
const newCard = await cardRepo.create(ctx.drizzleDb, {
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
createdBy: userId,
|
||||
@@ -76,7 +69,7 @@ export const cardRouter = createTRPCRouter({
|
||||
index,
|
||||
});
|
||||
|
||||
const newCardId = newCard?.id;
|
||||
const newCardId = newCard.id;
|
||||
|
||||
if (!newCardId)
|
||||
throw new TRPCError({
|
||||
@@ -84,12 +77,6 @@ export const cardRouter = createTRPCRouter({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.created",
|
||||
cardId: newCard.id,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
if (newCardId && input.labelPublicIds.length) {
|
||||
const labels = await labelRepo.getAllByPublicIds(
|
||||
ctx.db,
|
||||
|
||||
@@ -24,7 +24,7 @@ export const feedbackRouter = createTRPCRouter({
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const result = await feedbackRepo.create(ctx.adminDb, {
|
||||
const result = await feedbackRepo.create(ctx.db, {
|
||||
feedback: input.feedback,
|
||||
createdBy: userId,
|
||||
url: input.url,
|
||||
|
||||
@@ -73,12 +73,12 @@ export const memberRouter = createTRPCRouter({
|
||||
let hashedToken: string | undefined;
|
||||
let verificationType: string | undefined;
|
||||
|
||||
const existingUser = await userRepo.getByEmail(ctx.adminDb, input.email);
|
||||
const existingUser = await userRepo.getByEmail(ctx.db, input.email);
|
||||
|
||||
if (existingUser) {
|
||||
invitedUserId = existingUser.id;
|
||||
|
||||
const magicLink = await ctx.adminDb.auth.admin.generateLink({
|
||||
const magicLink = await ctx.db.auth.admin.generateLink({
|
||||
type: "magiclink",
|
||||
email: input.email,
|
||||
options: {
|
||||
@@ -89,7 +89,7 @@ export const memberRouter = createTRPCRouter({
|
||||
hashedToken = magicLink.data.properties?.hashed_token;
|
||||
verificationType = magicLink.data.properties?.verification_type;
|
||||
} else {
|
||||
const invite = await ctx.adminDb.auth.admin.generateLink({
|
||||
const invite = await ctx.db.auth.admin.generateLink({
|
||||
type: "invite",
|
||||
email: input.email,
|
||||
options: {
|
||||
@@ -111,7 +111,7 @@ export const memberRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
const newUser = await userRepo.create(ctx.adminDb, {
|
||||
const newUser = await userRepo.create(ctx.db, {
|
||||
email: invitedUserEmail,
|
||||
id: invitedUserAuthId,
|
||||
stripeCustomerId: stripeCustomer.id,
|
||||
|
||||
@@ -37,7 +37,7 @@ export const userRouter = createTRPCRouter({
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const result = await userRepo.getById(ctx.db, userId);
|
||||
const result = await userRepo.getById(ctx.drizzleDb, userId);
|
||||
|
||||
if (!result?.name) {
|
||||
throw new TRPCError({
|
||||
@@ -81,7 +81,7 @@ export const userRouter = createTRPCRouter({
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const result = await userRepo.update(ctx.adminDb, userId, input);
|
||||
const result = await userRepo.update(ctx.db, userId, input);
|
||||
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -126,7 +126,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
|
||||
const workspacePublicId = generateUID();
|
||||
|
||||
const result = await workspaceRepo.create(ctx.adminDb, {
|
||||
const result = await workspaceRepo.create(ctx.db, {
|
||||
publicId: workspacePublicId,
|
||||
name: input.name,
|
||||
slug: workspacePublicId,
|
||||
@@ -174,7 +174,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
);
|
||||
|
||||
const reservedOrPremiumWorkspaceSlug =
|
||||
await workspaceSlugRepo.getWorkspaceSlug(ctx.adminDb, input.slug);
|
||||
await workspaceSlugRepo.getWorkspaceSlug(ctx.db, input.slug);
|
||||
|
||||
const isWorkspaceSlugAvailable =
|
||||
await workspaceRepo.isWorkspaceSlugAvailable(ctx.db, input.slug);
|
||||
@@ -261,7 +261,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
const slug = input.workspaceSlug.toLowerCase();
|
||||
// check slug is not reserved
|
||||
const workspaceSlug = await workspaceSlugRepo.getWorkspaceSlug(
|
||||
ctx.adminDb,
|
||||
ctx.db,
|
||||
slug,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
// import type { Pool } from "@neondatabase/serverless";
|
||||
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
|
||||
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
|
||||
// import type { NeonDatabase as DrizzleClient } from "drizzle-orm/neon-serverless";
|
||||
import type { OpenApiMeta } from "trpc-to-openapi";
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import type { Database } from "@kan/db/types/database.types";
|
||||
import type { SupabaseClient } from "@kan/supabase";
|
||||
// import { createDrizzleClient } from "@kan/db/client";
|
||||
import {
|
||||
createNextApiClient,
|
||||
createTRPCAdminClient,
|
||||
createTRPCClient,
|
||||
} from "@kan/supabase";
|
||||
import { createDrizzleClient } from "@kan/db/client";
|
||||
import { createNextApiClient, createTRPCClient } from "@kan/supabase";
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
@@ -23,22 +18,14 @@ export interface User {
|
||||
interface CreateContextOptions {
|
||||
user: User | null;
|
||||
db: SupabaseClient<Database>;
|
||||
adminDb: SupabaseClient<Database>;
|
||||
// drizzleDb:
|
||||
// | (DrizzleClient<
|
||||
// typeof import("/Users/henryball/kan/packages/db/dist/schema")
|
||||
// > & {
|
||||
// $client: Pool;
|
||||
// })
|
||||
// | null;
|
||||
drizzleDb: dbClient;
|
||||
}
|
||||
|
||||
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
return {
|
||||
user: opts.user,
|
||||
db: opts.db,
|
||||
adminDb: opts.adminDb,
|
||||
// drizzleDb: opts.drizzleDb,
|
||||
drizzleDb: opts.drizzleDb,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,37 +34,35 @@ export const createTRPCContext = async ({
|
||||
resHeaders,
|
||||
}: FetchCreateContextFnOptions) => {
|
||||
const db = createTRPCClient(req, resHeaders);
|
||||
const adminDb = createTRPCAdminClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await db.auth.getUser();
|
||||
|
||||
// const drizzleDb = createDrizzleClient();
|
||||
const drizzleDb = createDrizzleClient();
|
||||
|
||||
return createInnerTRPCContext({ db, adminDb, user });
|
||||
return createInnerTRPCContext({ db, user, drizzleDb });
|
||||
};
|
||||
|
||||
export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||
const db = createNextApiClient(req);
|
||||
const adminDb = createTRPCAdminClient();
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
const accessToken = authHeader?.startsWith("Bearer ")
|
||||
? authHeader.substring(7)
|
||||
: null;
|
||||
|
||||
const drizzleDb = createDrizzleClient();
|
||||
|
||||
if (!accessToken) {
|
||||
return createInnerTRPCContext({ db, adminDb, user: null });
|
||||
return createInnerTRPCContext({ db, user: null, drizzleDb });
|
||||
}
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await db.auth.getUser(accessToken);
|
||||
|
||||
// const drizzleDb = createDrizzleClient();
|
||||
|
||||
return createInnerTRPCContext({ db, adminDb, user });
|
||||
return createInnerTRPCContext({ db, user, drizzleDb });
|
||||
};
|
||||
|
||||
const t = initTRPC
|
||||
|
||||
37
packages/db/migrations/0009_shallow_silver_surfer.sql
Normal file
37
packages/db/migrations/0009_shallow_silver_surfer.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
DROP POLICY "Allow access to boards in user's workspace or public boards" ON "board" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow inserting boards in user's workspace" ON "board" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow updating boards in user's workspace" ON "board" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow deleting boards in user's workspace" ON "board" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to card activity in user's workspace or public boards" ON "card_activity" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow inserting card activity in user's workspace" ON "card_activity" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to card workspace members in user's workspace" ON "_card_workspace_members" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to cards in user's workspace or public boards" ON "card" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow inserting cards in user's workspace" ON "card" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow updating cards in user's workspace" ON "card" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow deleting cards in user's workspace" ON "card" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to card labels in user's workspace or public boards" ON "_card_labels" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow inserting card labels in user's workspace" ON "_card_labels" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow updating card labels in user's workspace" ON "_card_labels" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow deleting card labels in user's workspace" ON "_card_labels" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to card comments in user's workspace or public boards" ON "card_comments" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow inserting comments on cards in user's workspace" ON "card_comments" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow updating own comments" ON "card_comments" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow deleting own comments" ON "card_comments" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to user's own imports" ON "import" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to labels in user's workspace or public boards" ON "label" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow inserting labels in user's workspace" ON "label" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow updating labels in user's workspace" ON "label" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow deleting labels in user's workspace" ON "label" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow access to lists in user's workspace or public boards" ON "list" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow inserting lists in user's workspace" ON "list" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow updating lists in user's workspace" ON "list" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow deleting lists in user's workspace" ON "list" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow viewing members in user's workspace" ON "user" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow members to view workspace membership" ON "workspace_members" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow admins to add workspace members" ON "workspace_members" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow admins to update workspace members" ON "workspace_members" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow admins to remove workspace members" ON "workspace_members" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow viewing user's workspaces" ON "workspace" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow updating user's workspaces" ON "workspace" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow deleting user's workspaces" ON "workspace" CASCADE;--> statement-breakpoint
|
||||
DROP POLICY "Allow authenticated users to create workspaces" ON "workspace" CASCADE;
|
||||
1651
packages/db/migrations/meta/0009_snapshot.json
Normal file
1651
packages/db/migrations/meta/0009_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -64,6 +64,13 @@
|
||||
"when": 1744906695652,
|
||||
"tag": "0008_mature_ravenous",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1745407291997,
|
||||
"tag": "0009_shallow_silver_surfer",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
"@kan/shared": "workspace:^",
|
||||
"@neondatabase/serverless": "^0.10.4",
|
||||
"@vercel/postgres": "^0.10.0",
|
||||
"drizzle-orm": "^0.36.4",
|
||||
"drizzle-orm": "^0.42.0",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { NeonDatabase as DrizzleClient } from "drizzle-orm/neon-serverless";
|
||||
import { Pool } from "@neondatabase/serverless";
|
||||
import { drizzle } from "drizzle-orm/neon-serverless";
|
||||
|
||||
import * as schema from "./schema";
|
||||
|
||||
export type dbClient = DrizzleClient<typeof schema> & {
|
||||
$client: Pool;
|
||||
};
|
||||
|
||||
export const createDrizzleClient = () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.POSTGRES_URL,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { and, eq, isNull, sql } from "drizzle-orm";
|
||||
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import type { Database } from "@kan/db/types/database.types";
|
||||
import * as schema from "@kan/db/schema";
|
||||
import { generateUID } from "@kan/shared/utils";
|
||||
|
||||
export const create = async (
|
||||
db: SupabaseClient<Database>,
|
||||
db: dbClient,
|
||||
cardInput: {
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -13,21 +16,56 @@ export const create = async (
|
||||
index: number;
|
||||
},
|
||||
) => {
|
||||
const { data } = await db
|
||||
.from("card")
|
||||
.insert({
|
||||
publicId: generateUID(),
|
||||
title: cardInput.title,
|
||||
description: cardInput.description,
|
||||
createdBy: cardInput.createdBy,
|
||||
listId: cardInput.listId,
|
||||
index: cardInput.index,
|
||||
})
|
||||
.select(`id`)
|
||||
.limit(1)
|
||||
.single();
|
||||
return db.transaction(async (tx) => {
|
||||
const getExistingCardAtIndex = async () =>
|
||||
tx.query.cards.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
},
|
||||
where: and(
|
||||
eq(schema.cards.listId, cardInput.listId),
|
||||
eq(schema.cards.index, cardInput.index),
|
||||
isNull(schema.cards.deletedAt),
|
||||
),
|
||||
});
|
||||
|
||||
return data;
|
||||
const existingCardAtIndex = await getExistingCardAtIndex();
|
||||
|
||||
if (existingCardAtIndex?.id) {
|
||||
await tx.execute(sql`
|
||||
UPDATE card
|
||||
SET index = index + 1
|
||||
WHERE "listId" = ${cardInput.listId} AND index >= ${cardInput.index} AND "deletedAt" IS NULL;
|
||||
`);
|
||||
|
||||
const refetchedExistingCardAtIndex = await getExistingCardAtIndex();
|
||||
|
||||
if (refetchedExistingCardAtIndex?.id) return tx.rollback();
|
||||
}
|
||||
|
||||
const result = await tx
|
||||
.insert(schema.cards)
|
||||
.values({
|
||||
publicId: generateUID(),
|
||||
title: cardInput.title,
|
||||
description: cardInput.description,
|
||||
createdBy: cardInput.createdBy,
|
||||
listId: cardInput.listId,
|
||||
index: cardInput.index,
|
||||
})
|
||||
.returning({ id: schema.cards.id });
|
||||
|
||||
if (!result[0]) return tx.rollback();
|
||||
|
||||
await tx.insert(schema.cardActivities).values({
|
||||
publicId: generateUID(),
|
||||
cardId: result[0].id,
|
||||
type: "card.created",
|
||||
createdBy: cardInput.createdBy,
|
||||
});
|
||||
|
||||
return result[0];
|
||||
});
|
||||
};
|
||||
|
||||
export const bulkCreateCardLabelRelationships = async (
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import type { Database } from "@kan/db/types/database.types";
|
||||
import * as schema from "@kan/db/schema";
|
||||
|
||||
export const getById = async (db: SupabaseClient<Database>, userId: string) => {
|
||||
const { data } = await db
|
||||
.from("user")
|
||||
.select(`id, name, email, image, stripeCustomerId`)
|
||||
.eq("id", userId)
|
||||
.limit(1)
|
||||
.single();
|
||||
export const getById = async (db: dbClient, userId: string) => {
|
||||
const data = await db.query.users.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
stripeCustomerId: true,
|
||||
},
|
||||
where: eq(schema.users.id, userId),
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
bigserial,
|
||||
index,
|
||||
pgEnum,
|
||||
pgPolicy,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
||||
|
||||
import { imports } from "./imports";
|
||||
import { labels } from "./labels";
|
||||
@@ -53,55 +51,6 @@ export const boards = pgTable(
|
||||
uniqueIndex("unique_slug_per_workspace")
|
||||
.on(table.workspaceId, table.slug)
|
||||
.where(sql`${table.deletedAt} IS NULL`),
|
||||
pgPolicy("Allow access to boards in user's workspace or public boards", {
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
"workspaceId" IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
OR visibility = 'public'
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow inserting boards in user's workspace", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
"workspaceId" IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow updating boards in user's workspace", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"workspaceId" IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow deleting boards in user's workspace", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"workspaceId" IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
integer,
|
||||
pgEnum,
|
||||
pgPolicy,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
text,
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
||||
|
||||
import { imports } from "./imports";
|
||||
import { labels } from "./labels";
|
||||
@@ -36,89 +34,24 @@ export const activityTypeEnum = pgEnum("card_activity_type", [
|
||||
"card.archived",
|
||||
]);
|
||||
|
||||
export const cards = pgTable(
|
||||
"card",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
listId: bigint("listId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => lists.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(
|
||||
() => imports.id,
|
||||
),
|
||||
},
|
||||
() => [
|
||||
pgPolicy("Allow access to cards in user's workspace or public boards", {
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
"listId" IN (
|
||||
SELECT l.id
|
||||
FROM list l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
OR b.visibility = 'public'
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow inserting cards in user's workspace", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
"listId" IN (
|
||||
SELECT l.id
|
||||
FROM list l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
|
||||
pgPolicy("Allow updating cards in user's workspace", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"listId" IN (
|
||||
SELECT l.id
|
||||
FROM list l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow deleting cards in user's workspace", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"listId" IN (
|
||||
SELECT l.id
|
||||
FROM list l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const cards = pgTable("card", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
listId: bigint("listId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => lists.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
}).enableRLS();
|
||||
|
||||
export const cardsRelations = relations(cards, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
@@ -142,76 +75,37 @@ export const cardsRelations = relations(cards, ({ one, many }) => ({
|
||||
comments: many(comments),
|
||||
}));
|
||||
|
||||
export const cardActivities = pgTable(
|
||||
"card_activity",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
type: activityTypeEnum("type").notNull(),
|
||||
cardId: bigint("cardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => cards.id, { onDelete: "cascade" }),
|
||||
fromIndex: integer("fromIndex"),
|
||||
toIndex: integer("toIndex"),
|
||||
fromListId: bigint("fromListId", { mode: "number" }).references(
|
||||
() => lists.id,
|
||||
),
|
||||
toListId: bigint("toListId", { mode: "number" }).references(() => lists.id),
|
||||
labelId: bigint("labelId", { mode: "number" }).references(() => labels.id),
|
||||
workspaceMemberId: bigint("workspaceMemberId", {
|
||||
mode: "number",
|
||||
}).references(() => workspaceMembers.id),
|
||||
fromTitle: varchar("fromTitle", { length: 255 }),
|
||||
toTitle: varchar("toTitle", { length: 255 }),
|
||||
fromDescription: text("fromDescription"),
|
||||
toDescription: text("toDescription"),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
commentId: bigint("commentId", { mode: "number" }).references(
|
||||
() => comments.id,
|
||||
),
|
||||
fromComment: text("fromComment"),
|
||||
toComment: text("toComment"),
|
||||
},
|
||||
() => [
|
||||
pgPolicy(
|
||||
"Allow access to card activity in user's workspace or public boards",
|
||||
{
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
OR b.visibility = 'public'
|
||||
)
|
||||
`,
|
||||
},
|
||||
),
|
||||
pgPolicy("Allow inserting card activity in user's workspace", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const cardActivities = pgTable("card_activity", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
type: activityTypeEnum("type").notNull(),
|
||||
cardId: bigint("cardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => cards.id, { onDelete: "cascade" }),
|
||||
fromIndex: integer("fromIndex"),
|
||||
toIndex: integer("toIndex"),
|
||||
fromListId: bigint("fromListId", { mode: "number" }).references(
|
||||
() => lists.id,
|
||||
),
|
||||
toListId: bigint("toListId", { mode: "number" }).references(() => lists.id),
|
||||
labelId: bigint("labelId", { mode: "number" }).references(() => labels.id),
|
||||
workspaceMemberId: bigint("workspaceMemberId", {
|
||||
mode: "number",
|
||||
}).references(() => workspaceMembers.id),
|
||||
fromTitle: varchar("fromTitle", { length: 255 }),
|
||||
toTitle: varchar("toTitle", { length: 255 }),
|
||||
fromDescription: text("fromDescription"),
|
||||
toDescription: text("toDescription"),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
commentId: bigint("commentId", { mode: "number" }).references(
|
||||
() => comments.id,
|
||||
),
|
||||
fromComment: text("fromComment"),
|
||||
toComment: text("toComment"),
|
||||
}).enableRLS();
|
||||
|
||||
export const cardActivitiesRelations = relations(cardActivities, ({ one }) => ({
|
||||
card: one(cards, {
|
||||
@@ -250,106 +144,7 @@ export const cardsToLabels = pgTable(
|
||||
.notNull()
|
||||
.references(() => labels.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.cardId, t.labelId] }),
|
||||
pgPolicy(
|
||||
"Allow access to card labels in user's workspace or public boards",
|
||||
{
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid()
|
||||
WHERE wm."userId" = auth.uid()
|
||||
OR b.visibility = 'public'
|
||||
)
|
||||
AND
|
||||
"labelId" IN (
|
||||
SELECT l.id
|
||||
FROM label l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid()
|
||||
WHERE wm."userId" = auth.uid()
|
||||
OR b.visibility = 'public'
|
||||
)
|
||||
`,
|
||||
},
|
||||
),
|
||||
pgPolicy("Allow inserting card labels in user's workspace", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
AND
|
||||
"labelId" IN (
|
||||
SELECT l.id
|
||||
FROM label l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow updating card labels in user's workspace", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
AND
|
||||
"labelId" IN (
|
||||
SELECT l.id
|
||||
FROM label l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow deleting card labels in user's workspace", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
AND
|
||||
"labelId" IN (
|
||||
SELECT l.id
|
||||
FROM label l
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
(t) => [primaryKey({ columns: [t.cardId, t.labelId] })],
|
||||
).enableRLS();
|
||||
|
||||
export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({
|
||||
@@ -373,34 +168,7 @@ export const cardToWorkspaceMembers = pgTable(
|
||||
.notNull()
|
||||
.references(() => workspaceMembers.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.cardId, t.workspaceMemberId] }),
|
||||
pgPolicy("Allow access to card workspace members in user's workspace", {
|
||||
for: "all",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
AND
|
||||
"workspaceMemberId" IN (
|
||||
SELECT wm.id
|
||||
FROM workspace_members wm
|
||||
WHERE wm."workspaceId" IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
(t) => [primaryKey({ columns: [t.cardId, t.workspaceMemberId] })],
|
||||
).enableRLS();
|
||||
|
||||
export const cardToWorkspaceMembersRelations = relations(
|
||||
@@ -417,76 +185,21 @@ export const cardToWorkspaceMembersRelations = relations(
|
||||
}),
|
||||
);
|
||||
|
||||
export const comments = pgTable(
|
||||
"card_comments",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
comment: text("comment").notNull(),
|
||||
cardId: bigint("cardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => cards.id, { onDelete: "cascade" }),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
},
|
||||
() => [
|
||||
pgPolicy(
|
||||
"Allow access to card comments in user's workspace or public boards",
|
||||
{
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
OR b.visibility = 'public'
|
||||
)
|
||||
`,
|
||||
},
|
||||
),
|
||||
pgPolicy("Allow inserting comments on cards in user's workspace", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
"cardId" IN (
|
||||
SELECT c.id
|
||||
FROM card c
|
||||
JOIN list l ON c."listId" = l.id
|
||||
JOIN board b ON l."boardId" = b.id
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow updating own comments", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"createdBy" = auth.uid()
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow deleting own comments", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"createdBy" = auth.uid()
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const comments = pgTable("card_comments", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
comment: text("comment").notNull(),
|
||||
cardId: bigint("cardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => cards.id, { onDelete: "cascade" }),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
}).enableRLS();
|
||||
|
||||
export const commentsRelations = relations(comments, ({ one }) => ({
|
||||
card: one(cards, {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigserial,
|
||||
pgEnum,
|
||||
pgPolicy,
|
||||
pgTable,
|
||||
timestamp,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { authenticatedRole } from "drizzle-orm/supabase";
|
||||
|
||||
import { boards } from "./boards";
|
||||
import { cards } from "./cards";
|
||||
@@ -23,29 +21,16 @@ export const importStatusEnum = pgEnum("status", [
|
||||
"failed",
|
||||
]);
|
||||
|
||||
export const imports = pgTable(
|
||||
"import",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
source: importSourceEnum("source").notNull(),
|
||||
status: importStatusEnum("status").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
},
|
||||
() => [
|
||||
pgPolicy("Allow access to user's own imports", {
|
||||
for: "all",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"createdBy" = auth.uid()
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const imports = pgTable("import", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
source: importSourceEnum("source").notNull(),
|
||||
status: importStatusEnum("status").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
}).enableRLS();
|
||||
|
||||
export const importsRelations = relations(imports, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
|
||||
@@ -1,95 +1,33 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
pgPolicy,
|
||||
pgTable,
|
||||
timestamp,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
||||
|
||||
import { boards } from "./boards";
|
||||
import { cardsToLabels } from "./cards";
|
||||
import { imports } from "./imports";
|
||||
import { users } from "./users";
|
||||
|
||||
export const labels = pgTable(
|
||||
"label",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
colourCode: varchar("colourCode", { length: 12 }),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
boardId: bigint("boardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(
|
||||
() => imports.id,
|
||||
),
|
||||
},
|
||||
() => [
|
||||
pgPolicy("Allow access to labels in user's workspace or public boards", {
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
OR b.visibility = 'public'
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow inserting labels in user's workspace", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow updating labels in user's workspace", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow deleting labels in user's workspace", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const labels = pgTable("label", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
colourCode: varchar("colourCode", { length: 12 }),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
boardId: bigint("boardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
}).enableRLS();
|
||||
|
||||
export const labelsRelations = relations(labels, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
|
||||
@@ -1,98 +1,36 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
integer,
|
||||
pgPolicy,
|
||||
pgTable,
|
||||
timestamp,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
||||
|
||||
import { boards } from "./boards";
|
||||
import { cards } from "./cards";
|
||||
import { imports } from "./imports";
|
||||
import { users } from "./users";
|
||||
|
||||
export const lists = pgTable(
|
||||
"list",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
boardId: bigint("boardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(
|
||||
() => imports.id,
|
||||
),
|
||||
},
|
||||
() => [
|
||||
pgPolicy("Allow access to lists in user's workspace or public boards", {
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
OR b.visibility = 'public'
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow inserting lists in user's workspace", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow updating lists in user's workspace", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow deleting lists in user's workspace", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"boardId" IN (
|
||||
SELECT b.id
|
||||
FROM board b
|
||||
JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId"
|
||||
WHERE wm."userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const lists = pgTable("list", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
boardId: bigint("boardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
}).enableRLS();
|
||||
|
||||
export const listsRelations = relations(lists, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import {
|
||||
pgPolicy,
|
||||
pgTable,
|
||||
timestamp,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { authenticatedRole } from "drizzle-orm/supabase";
|
||||
import { relations } from "drizzle-orm";
|
||||
import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
|
||||
|
||||
import { boards } from "./boards";
|
||||
import { cards } from "./cards";
|
||||
@@ -14,35 +7,14 @@ import { imports } from "./imports";
|
||||
import { lists } from "./lists";
|
||||
import { workspaceMembers, workspaces } from "./workspaces";
|
||||
|
||||
export const users = pgTable(
|
||||
"user",
|
||||
{
|
||||
id: uuid("id").notNull().primaryKey(),
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 255 }).notNull().unique(),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
image: varchar("image", { length: 255 }),
|
||||
stripeCustomerId: varchar("stripeCustomerId", { length: 255 }),
|
||||
},
|
||||
() => [
|
||||
pgPolicy("Allow viewing members in user's workspace", {
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
id IN (
|
||||
SELECT wm."userId"
|
||||
FROM workspace_members wm
|
||||
WHERE wm."workspaceId" IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
)
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const users = pgTable("user", {
|
||||
id: uuid("id").notNull().primaryKey(),
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 255 }).notNull().unique(),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
image: varchar("image", { length: 255 }),
|
||||
stripeCustomerId: varchar("stripeCustomerId", { length: 255 }),
|
||||
}).enableRLS();
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
boards: many(boards),
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
pgEnum,
|
||||
pgPolicy,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
||||
|
||||
import { users } from "./users";
|
||||
|
||||
@@ -27,140 +25,44 @@ export const workspacePlanEnum = pgEnum("workspace_plan", [
|
||||
"enterprise",
|
||||
]);
|
||||
|
||||
export const workspaces = pgTable(
|
||||
"workspace",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||
plan: workspacePlanEnum("plan").notNull().default("free"),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
},
|
||||
() => [
|
||||
pgPolicy("Allow viewing user's workspaces", {
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole, anonRole],
|
||||
using: sql`
|
||||
CASE
|
||||
WHEN auth.uid() IS NULL THEN
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM board
|
||||
WHERE "workspaceId" = workspace.id
|
||||
AND visibility = 'public'
|
||||
)
|
||||
ELSE
|
||||
id IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
OR "createdBy" = auth.uid()
|
||||
END
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow updating user's workspaces", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
id IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow deleting user's workspaces", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
id IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow authenticated users to create workspaces", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`true`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const workspaces = pgTable("workspace", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||
plan: workspacePlanEnum("plan").notNull().default("free"),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
}).enableRLS();
|
||||
|
||||
export const workspaceRelations = relations(workspaces, ({ one, many }) => ({
|
||||
user: one(users, { fields: [workspaces.createdBy], references: [users.id] }),
|
||||
members: many(workspaceMembers),
|
||||
}));
|
||||
|
||||
export const workspaceMembers = pgTable(
|
||||
"workspace_members",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
userId: uuid("userId")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||
createdBy: uuid("createdBy").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
role: memberRoleEnum("role").notNull(),
|
||||
status: memberStatusEnum("status").default("invited").notNull(),
|
||||
},
|
||||
() => [
|
||||
pgPolicy("Allow members to view workspace membership", {
|
||||
for: "select",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
"userId" = auth.uid() OR
|
||||
is_workspace_member(auth.uid(), "workspaceId")
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow admins to add workspace members", {
|
||||
for: "insert",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
withCheck: sql`
|
||||
is_workspace_admin(auth.uid(), "workspaceId")
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow admins to update workspace members", {
|
||||
for: "update",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
is_workspace_admin(auth.uid(), "workspaceId")
|
||||
`,
|
||||
}),
|
||||
pgPolicy("Allow admins to remove workspace members", {
|
||||
for: "delete",
|
||||
as: "permissive",
|
||||
to: [authenticatedRole],
|
||||
using: sql`
|
||||
is_workspace_admin(auth.uid(), "workspaceId")
|
||||
`,
|
||||
}),
|
||||
],
|
||||
).enableRLS();
|
||||
export const workspaceMembers = pgTable("workspace_members", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
userId: uuid("userId")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||
createdBy: uuid("createdBy").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
role: memberRoleEnum("role").notNull(),
|
||||
status: memberStatusEnum("status").default("invited").notNull(),
|
||||
}).enableRLS();
|
||||
|
||||
export const slugs = pgTable("workspace_slugs", {
|
||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||
|
||||
@@ -74,15 +74,15 @@ export function createNextApiClient(req: NextApiRequest) {
|
||||
|
||||
export function createTRPCClient(req: Request, resHeaders: Headers) {
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_API_KEY;
|
||||
|
||||
if (!supabaseUrl || !supabaseKey) {
|
||||
if (!supabaseUrl || !serviceKey) {
|
||||
throw new Error("Missing Supabase environment variables");
|
||||
}
|
||||
|
||||
const supabase = createServerClient<Database, "public">(
|
||||
supabaseUrl,
|
||||
supabaseKey,
|
||||
serviceKey,
|
||||
{
|
||||
cookies: {
|
||||
get(name: string) {
|
||||
@@ -101,27 +101,3 @@ export function createTRPCClient(req: Request, resHeaders: Headers) {
|
||||
|
||||
return supabase;
|
||||
}
|
||||
|
||||
export function createTRPCAdminClient() {
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_API_KEY;
|
||||
|
||||
if (!supabaseUrl || !serviceKey) {
|
||||
throw new Error("Missing Supabase environment variables");
|
||||
}
|
||||
|
||||
const supabase = createServerClient<Database, "public">(
|
||||
supabaseUrl,
|
||||
serviceKey,
|
||||
{
|
||||
cookies: {
|
||||
get: (_name: string) => "",
|
||||
set: (_name: string, _value: string, _options: CookieOptions) =>
|
||||
undefined,
|
||||
remove: (_name: string, _options: CookieOptions) => undefined,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return supabase;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user