refactor: reinstate drizzle
This commit is contained in:
@@ -57,18 +57,11 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
|
||||||
if (list.cards.length) {
|
if (list.cards.length && input.position === "end" && lastCard) {
|
||||||
if (input.position === "end" && lastCard) index = lastCard.index + 1;
|
index = lastCard.index + 1;
|
||||||
|
|
||||||
if (input.position === "start") {
|
|
||||||
await cardRepo.pushIndex(ctx.db, {
|
|
||||||
listId: list.id,
|
|
||||||
cardIndex: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCard = await cardRepo.create(ctx.db, {
|
const newCard = await cardRepo.create(ctx.drizzleDb, {
|
||||||
title: input.title,
|
title: input.title,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
createdBy: userId,
|
createdBy: userId,
|
||||||
@@ -76,7 +69,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
index,
|
index,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newCardId = newCard?.id;
|
const newCardId = newCard.id;
|
||||||
|
|
||||||
if (!newCardId)
|
if (!newCardId)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -84,12 +77,6 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
});
|
});
|
||||||
|
|
||||||
await cardActivityRepo.create(ctx.db, {
|
|
||||||
type: "card.created",
|
|
||||||
cardId: newCard.id,
|
|
||||||
createdBy: userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (newCardId && input.labelPublicIds.length) {
|
if (newCardId && input.labelPublicIds.length) {
|
||||||
const labels = await labelRepo.getAllByPublicIds(
|
const labels = await labelRepo.getAllByPublicIds(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const feedbackRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await feedbackRepo.create(ctx.adminDb, {
|
const result = await feedbackRepo.create(ctx.db, {
|
||||||
feedback: input.feedback,
|
feedback: input.feedback,
|
||||||
createdBy: userId,
|
createdBy: userId,
|
||||||
url: input.url,
|
url: input.url,
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ export const memberRouter = createTRPCRouter({
|
|||||||
let hashedToken: string | undefined;
|
let hashedToken: string | undefined;
|
||||||
let verificationType: 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) {
|
if (existingUser) {
|
||||||
invitedUserId = existingUser.id;
|
invitedUserId = existingUser.id;
|
||||||
|
|
||||||
const magicLink = await ctx.adminDb.auth.admin.generateLink({
|
const magicLink = await ctx.db.auth.admin.generateLink({
|
||||||
type: "magiclink",
|
type: "magiclink",
|
||||||
email: input.email,
|
email: input.email,
|
||||||
options: {
|
options: {
|
||||||
@@ -89,7 +89,7 @@ export const memberRouter = createTRPCRouter({
|
|||||||
hashedToken = magicLink.data.properties?.hashed_token;
|
hashedToken = magicLink.data.properties?.hashed_token;
|
||||||
verificationType = magicLink.data.properties?.verification_type;
|
verificationType = magicLink.data.properties?.verification_type;
|
||||||
} else {
|
} else {
|
||||||
const invite = await ctx.adminDb.auth.admin.generateLink({
|
const invite = await ctx.db.auth.admin.generateLink({
|
||||||
type: "invite",
|
type: "invite",
|
||||||
email: input.email,
|
email: input.email,
|
||||||
options: {
|
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,
|
email: invitedUserEmail,
|
||||||
id: invitedUserAuthId,
|
id: invitedUserAuthId,
|
||||||
stripeCustomerId: stripeCustomer.id,
|
stripeCustomerId: stripeCustomer.id,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await userRepo.getById(ctx.db, userId);
|
const result = await userRepo.getById(ctx.drizzleDb, userId);
|
||||||
|
|
||||||
if (!result?.name) {
|
if (!result?.name) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -81,7 +81,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await userRepo.update(ctx.adminDb, userId, input);
|
const result = await userRepo.update(ctx.db, userId, input);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const workspacePublicId = generateUID();
|
const workspacePublicId = generateUID();
|
||||||
|
|
||||||
const result = await workspaceRepo.create(ctx.adminDb, {
|
const result = await workspaceRepo.create(ctx.db, {
|
||||||
publicId: workspacePublicId,
|
publicId: workspacePublicId,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
slug: workspacePublicId,
|
slug: workspacePublicId,
|
||||||
@@ -174,7 +174,7 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const reservedOrPremiumWorkspaceSlug =
|
const reservedOrPremiumWorkspaceSlug =
|
||||||
await workspaceSlugRepo.getWorkspaceSlug(ctx.adminDb, input.slug);
|
await workspaceSlugRepo.getWorkspaceSlug(ctx.db, input.slug);
|
||||||
|
|
||||||
const isWorkspaceSlugAvailable =
|
const isWorkspaceSlugAvailable =
|
||||||
await workspaceRepo.isWorkspaceSlugAvailable(ctx.db, input.slug);
|
await workspaceRepo.isWorkspaceSlugAvailable(ctx.db, input.slug);
|
||||||
@@ -261,7 +261,7 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
const slug = input.workspaceSlug.toLowerCase();
|
const slug = input.workspaceSlug.toLowerCase();
|
||||||
// check slug is not reserved
|
// check slug is not reserved
|
||||||
const workspaceSlug = await workspaceSlugRepo.getWorkspaceSlug(
|
const workspaceSlug = await workspaceSlugRepo.getWorkspaceSlug(
|
||||||
ctx.adminDb,
|
ctx.db,
|
||||||
slug,
|
slug,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
// import type { Pool } from "@neondatabase/serverless";
|
|
||||||
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
|
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
|
||||||
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
|
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 type { OpenApiMeta } from "trpc-to-openapi";
|
||||||
import { initTRPC, TRPCError } from "@trpc/server";
|
import { initTRPC, TRPCError } from "@trpc/server";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
|
import type { dbClient } from "@kan/db/client";
|
||||||
import type { Database } from "@kan/db/types/database.types";
|
import type { Database } from "@kan/db/types/database.types";
|
||||||
import type { SupabaseClient } from "@kan/supabase";
|
import type { SupabaseClient } from "@kan/supabase";
|
||||||
// import { createDrizzleClient } from "@kan/db/client";
|
import { createDrizzleClient } from "@kan/db/client";
|
||||||
import {
|
import { createNextApiClient, createTRPCClient } from "@kan/supabase";
|
||||||
createNextApiClient,
|
|
||||||
createTRPCAdminClient,
|
|
||||||
createTRPCClient,
|
|
||||||
} from "@kan/supabase";
|
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -23,22 +18,14 @@ export interface User {
|
|||||||
interface CreateContextOptions {
|
interface CreateContextOptions {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
db: SupabaseClient<Database>;
|
db: SupabaseClient<Database>;
|
||||||
adminDb: SupabaseClient<Database>;
|
drizzleDb: dbClient;
|
||||||
// drizzleDb:
|
|
||||||
// | (DrizzleClient<
|
|
||||||
// typeof import("/Users/henryball/kan/packages/db/dist/schema")
|
|
||||||
// > & {
|
|
||||||
// $client: Pool;
|
|
||||||
// })
|
|
||||||
// | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||||
return {
|
return {
|
||||||
user: opts.user,
|
user: opts.user,
|
||||||
db: opts.db,
|
db: opts.db,
|
||||||
adminDb: opts.adminDb,
|
drizzleDb: opts.drizzleDb,
|
||||||
// drizzleDb: opts.drizzleDb,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,37 +34,35 @@ export const createTRPCContext = async ({
|
|||||||
resHeaders,
|
resHeaders,
|
||||||
}: FetchCreateContextFnOptions) => {
|
}: FetchCreateContextFnOptions) => {
|
||||||
const db = createTRPCClient(req, resHeaders);
|
const db = createTRPCClient(req, resHeaders);
|
||||||
const adminDb = createTRPCAdminClient();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { user },
|
data: { user },
|
||||||
} = await db.auth.getUser();
|
} = 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) => {
|
export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||||
const db = createNextApiClient(req);
|
const db = createNextApiClient(req);
|
||||||
const adminDb = createTRPCAdminClient();
|
|
||||||
|
|
||||||
const authHeader = req.headers.authorization;
|
const authHeader = req.headers.authorization;
|
||||||
const accessToken = authHeader?.startsWith("Bearer ")
|
const accessToken = authHeader?.startsWith("Bearer ")
|
||||||
? authHeader.substring(7)
|
? authHeader.substring(7)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const drizzleDb = createDrizzleClient();
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
return createInnerTRPCContext({ db, adminDb, user: null });
|
return createInnerTRPCContext({ db, user: null, drizzleDb });
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { user },
|
data: { user },
|
||||||
} = await db.auth.getUser(accessToken);
|
} = await db.auth.getUser(accessToken);
|
||||||
|
|
||||||
// const drizzleDb = createDrizzleClient();
|
return createInnerTRPCContext({ db, user, drizzleDb });
|
||||||
|
|
||||||
return createInnerTRPCContext({ db, adminDb, user });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const t = initTRPC
|
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,
|
"when": 1744906695652,
|
||||||
"tag": "0008_mature_ravenous",
|
"tag": "0008_mature_ravenous",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1745407291997,
|
||||||
|
"tag": "0009_shallow_silver_surfer",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"@kan/shared": "workspace:^",
|
"@kan/shared": "workspace:^",
|
||||||
"@neondatabase/serverless": "^0.10.4",
|
"@neondatabase/serverless": "^0.10.4",
|
||||||
"@vercel/postgres": "^0.10.0",
|
"@vercel/postgres": "^0.10.0",
|
||||||
"drizzle-orm": "^0.36.4",
|
"drizzle-orm": "^0.42.0",
|
||||||
"drizzle-zod": "^0.5.1",
|
"drizzle-zod": "^0.5.1",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
import type { NeonDatabase as DrizzleClient } from "drizzle-orm/neon-serverless";
|
||||||
import { Pool } from "@neondatabase/serverless";
|
import { Pool } from "@neondatabase/serverless";
|
||||||
import { drizzle } from "drizzle-orm/neon-serverless";
|
import { drizzle } from "drizzle-orm/neon-serverless";
|
||||||
|
|
||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
|
|
||||||
|
export type dbClient = DrizzleClient<typeof schema> & {
|
||||||
|
$client: Pool;
|
||||||
|
};
|
||||||
|
|
||||||
export const createDrizzleClient = () => {
|
export const createDrizzleClient = () => {
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
connectionString: process.env.POSTGRES_URL,
|
connectionString: process.env.POSTGRES_URL,
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
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 type { Database } from "@kan/db/types/database.types";
|
||||||
|
import * as schema from "@kan/db/schema";
|
||||||
import { generateUID } from "@kan/shared/utils";
|
import { generateUID } from "@kan/shared/utils";
|
||||||
|
|
||||||
export const create = async (
|
export const create = async (
|
||||||
db: SupabaseClient<Database>,
|
db: dbClient,
|
||||||
cardInput: {
|
cardInput: {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -13,21 +16,56 @@ export const create = async (
|
|||||||
index: number;
|
index: number;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const { data } = await db
|
return db.transaction(async (tx) => {
|
||||||
.from("card")
|
const getExistingCardAtIndex = async () =>
|
||||||
.insert({
|
tx.query.cards.findFirst({
|
||||||
publicId: generateUID(),
|
columns: {
|
||||||
title: cardInput.title,
|
id: true,
|
||||||
description: cardInput.description,
|
},
|
||||||
createdBy: cardInput.createdBy,
|
where: and(
|
||||||
listId: cardInput.listId,
|
eq(schema.cards.listId, cardInput.listId),
|
||||||
index: cardInput.index,
|
eq(schema.cards.index, cardInput.index),
|
||||||
})
|
isNull(schema.cards.deletedAt),
|
||||||
.select(`id`)
|
),
|
||||||
.limit(1)
|
});
|
||||||
.single();
|
|
||||||
|
|
||||||
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 (
|
export const bulkCreateCardLabelRelationships = async (
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
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 type { Database } from "@kan/db/types/database.types";
|
||||||
|
import * as schema from "@kan/db/schema";
|
||||||
|
|
||||||
export const getById = async (db: SupabaseClient<Database>, userId: string) => {
|
export const getById = async (db: dbClient, userId: string) => {
|
||||||
const { data } = await db
|
const data = await db.query.users.findFirst({
|
||||||
.from("user")
|
columns: {
|
||||||
.select(`id, name, email, image, stripeCustomerId`)
|
id: true,
|
||||||
.eq("id", userId)
|
name: true,
|
||||||
.limit(1)
|
email: true,
|
||||||
.single();
|
image: true,
|
||||||
|
stripeCustomerId: true,
|
||||||
|
},
|
||||||
|
where: eq(schema.users.id, userId),
|
||||||
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
bigserial,
|
bigserial,
|
||||||
index,
|
index,
|
||||||
pgEnum,
|
pgEnum,
|
||||||
pgPolicy,
|
|
||||||
pgTable,
|
pgTable,
|
||||||
text,
|
text,
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -12,7 +11,6 @@ import {
|
|||||||
uuid,
|
uuid,
|
||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
|
||||||
|
|
||||||
import { imports } from "./imports";
|
import { imports } from "./imports";
|
||||||
import { labels } from "./labels";
|
import { labels } from "./labels";
|
||||||
@@ -53,55 +51,6 @@ export const boards = pgTable(
|
|||||||
uniqueIndex("unique_slug_per_workspace")
|
uniqueIndex("unique_slug_per_workspace")
|
||||||
.on(table.workspaceId, table.slug)
|
.on(table.workspaceId, table.slug)
|
||||||
.where(sql`${table.deletedAt} IS NULL`),
|
.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();
|
).enableRLS();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { relations, sql } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
bigint,
|
bigint,
|
||||||
bigserial,
|
bigserial,
|
||||||
integer,
|
integer,
|
||||||
pgEnum,
|
pgEnum,
|
||||||
pgPolicy,
|
|
||||||
pgTable,
|
pgTable,
|
||||||
primaryKey,
|
primaryKey,
|
||||||
text,
|
text,
|
||||||
@@ -12,7 +11,6 @@ import {
|
|||||||
uuid,
|
uuid,
|
||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
|
||||||
|
|
||||||
import { imports } from "./imports";
|
import { imports } from "./imports";
|
||||||
import { labels } from "./labels";
|
import { labels } from "./labels";
|
||||||
@@ -36,89 +34,24 @@ export const activityTypeEnum = pgEnum("card_activity_type", [
|
|||||||
"card.archived",
|
"card.archived",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const cards = pgTable(
|
export const cards = pgTable("card", {
|
||||||
"card",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
title: varchar("title", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
description: text("description"),
|
||||||
title: varchar("title", { length: 255 }).notNull(),
|
index: integer("index").notNull(),
|
||||||
description: text("description"),
|
createdBy: uuid("createdBy")
|
||||||
index: integer("index").notNull(),
|
.notNull()
|
||||||
createdBy: uuid("createdBy")
|
.references(() => users.id),
|
||||||
.notNull()
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
.references(() => users.id),
|
updatedAt: timestamp("updatedAt"),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
deletedAt: timestamp("deletedAt"),
|
||||||
updatedAt: timestamp("updatedAt"),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
deletedAt: timestamp("deletedAt"),
|
listId: bigint("listId", { mode: "number" })
|
||||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
.notNull()
|
||||||
listId: bigint("listId", { mode: "number" })
|
.references(() => lists.id, { onDelete: "cascade" }),
|
||||||
.notNull()
|
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||||
.references(() => lists.id, { onDelete: "cascade" }),
|
}).enableRLS();
|
||||||
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 cardsRelations = relations(cards, ({ one, many }) => ({
|
export const cardsRelations = relations(cards, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
@@ -142,76 +75,37 @@ export const cardsRelations = relations(cards, ({ one, many }) => ({
|
|||||||
comments: many(comments),
|
comments: many(comments),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const cardActivities = pgTable(
|
export const cardActivities = pgTable("card_activity", {
|
||||||
"card_activity",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
type: activityTypeEnum("type").notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
cardId: bigint("cardId", { mode: "number" })
|
||||||
type: activityTypeEnum("type").notNull(),
|
.notNull()
|
||||||
cardId: bigint("cardId", { mode: "number" })
|
.references(() => cards.id, { onDelete: "cascade" }),
|
||||||
.notNull()
|
fromIndex: integer("fromIndex"),
|
||||||
.references(() => cards.id, { onDelete: "cascade" }),
|
toIndex: integer("toIndex"),
|
||||||
fromIndex: integer("fromIndex"),
|
fromListId: bigint("fromListId", { mode: "number" }).references(
|
||||||
toIndex: integer("toIndex"),
|
() => lists.id,
|
||||||
fromListId: bigint("fromListId", { mode: "number" }).references(
|
),
|
||||||
() => lists.id,
|
toListId: bigint("toListId", { mode: "number" }).references(() => lists.id),
|
||||||
),
|
labelId: bigint("labelId", { mode: "number" }).references(() => labels.id),
|
||||||
toListId: bigint("toListId", { mode: "number" }).references(() => lists.id),
|
workspaceMemberId: bigint("workspaceMemberId", {
|
||||||
labelId: bigint("labelId", { mode: "number" }).references(() => labels.id),
|
mode: "number",
|
||||||
workspaceMemberId: bigint("workspaceMemberId", {
|
}).references(() => workspaceMembers.id),
|
||||||
mode: "number",
|
fromTitle: varchar("fromTitle", { length: 255 }),
|
||||||
}).references(() => workspaceMembers.id),
|
toTitle: varchar("toTitle", { length: 255 }),
|
||||||
fromTitle: varchar("fromTitle", { length: 255 }),
|
fromDescription: text("fromDescription"),
|
||||||
toTitle: varchar("toTitle", { length: 255 }),
|
toDescription: text("toDescription"),
|
||||||
fromDescription: text("fromDescription"),
|
createdBy: uuid("createdBy")
|
||||||
toDescription: text("toDescription"),
|
.notNull()
|
||||||
createdBy: uuid("createdBy")
|
.references(() => users.id),
|
||||||
.notNull()
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
.references(() => users.id),
|
commentId: bigint("commentId", { mode: "number" }).references(
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
() => comments.id,
|
||||||
commentId: bigint("commentId", { mode: "number" }).references(
|
),
|
||||||
() => comments.id,
|
fromComment: text("fromComment"),
|
||||||
),
|
toComment: text("toComment"),
|
||||||
fromComment: text("fromComment"),
|
}).enableRLS();
|
||||||
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 cardActivitiesRelations = relations(cardActivities, ({ one }) => ({
|
export const cardActivitiesRelations = relations(cardActivities, ({ one }) => ({
|
||||||
card: one(cards, {
|
card: one(cards, {
|
||||||
@@ -250,106 +144,7 @@ export const cardsToLabels = pgTable(
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => labels.id, { onDelete: "cascade" }),
|
.references(() => labels.id, { onDelete: "cascade" }),
|
||||||
},
|
},
|
||||||
(t) => [
|
(t) => [primaryKey({ columns: [t.cardId, t.labelId] })],
|
||||||
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()
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
).enableRLS();
|
).enableRLS();
|
||||||
|
|
||||||
export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({
|
export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({
|
||||||
@@ -373,34 +168,7 @@ export const cardToWorkspaceMembers = pgTable(
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => workspaceMembers.id, { onDelete: "cascade" }),
|
.references(() => workspaceMembers.id, { onDelete: "cascade" }),
|
||||||
},
|
},
|
||||||
(t) => [
|
(t) => [primaryKey({ columns: [t.cardId, t.workspaceMemberId] })],
|
||||||
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()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
).enableRLS();
|
).enableRLS();
|
||||||
|
|
||||||
export const cardToWorkspaceMembersRelations = relations(
|
export const cardToWorkspaceMembersRelations = relations(
|
||||||
@@ -417,76 +185,21 @@ export const cardToWorkspaceMembersRelations = relations(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const comments = pgTable(
|
export const comments = pgTable("card_comments", {
|
||||||
"card_comments",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
comment: text("comment").notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
cardId: bigint("cardId", { mode: "number" })
|
||||||
comment: text("comment").notNull(),
|
.notNull()
|
||||||
cardId: bigint("cardId", { mode: "number" })
|
.references(() => cards.id, { onDelete: "cascade" }),
|
||||||
.notNull()
|
createdBy: uuid("createdBy")
|
||||||
.references(() => cards.id, { onDelete: "cascade" }),
|
.notNull()
|
||||||
createdBy: uuid("createdBy")
|
.references(() => users.id),
|
||||||
.notNull()
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
.references(() => users.id),
|
updatedAt: timestamp("updatedAt"),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
deletedAt: timestamp("deletedAt"),
|
||||||
updatedAt: timestamp("updatedAt"),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
deletedAt: timestamp("deletedAt"),
|
}).enableRLS();
|
||||||
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 commentsRelations = relations(comments, ({ one }) => ({
|
export const commentsRelations = relations(comments, ({ one }) => ({
|
||||||
card: one(cards, {
|
card: one(cards, {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { relations, sql } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
bigserial,
|
bigserial,
|
||||||
pgEnum,
|
pgEnum,
|
||||||
pgPolicy,
|
|
||||||
pgTable,
|
pgTable,
|
||||||
timestamp,
|
timestamp,
|
||||||
uuid,
|
uuid,
|
||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { authenticatedRole } from "drizzle-orm/supabase";
|
|
||||||
|
|
||||||
import { boards } from "./boards";
|
import { boards } from "./boards";
|
||||||
import { cards } from "./cards";
|
import { cards } from "./cards";
|
||||||
@@ -23,29 +21,16 @@ export const importStatusEnum = pgEnum("status", [
|
|||||||
"failed",
|
"failed",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const imports = pgTable(
|
export const imports = pgTable("import", {
|
||||||
"import",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
source: importSourceEnum("source").notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
status: importStatusEnum("status").notNull(),
|
||||||
source: importSourceEnum("source").notNull(),
|
createdBy: uuid("createdBy")
|
||||||
status: importStatusEnum("status").notNull(),
|
.notNull()
|
||||||
createdBy: uuid("createdBy")
|
.references(() => users.id),
|
||||||
.notNull()
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
.references(() => users.id),
|
}).enableRLS();
|
||||||
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 importsRelations = relations(imports, ({ one, many }) => ({
|
export const importsRelations = relations(imports, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
|
|||||||
@@ -1,95 +1,33 @@
|
|||||||
import { relations, sql } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
bigint,
|
bigint,
|
||||||
bigserial,
|
bigserial,
|
||||||
pgPolicy,
|
|
||||||
pgTable,
|
pgTable,
|
||||||
timestamp,
|
timestamp,
|
||||||
uuid,
|
uuid,
|
||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
|
||||||
|
|
||||||
import { boards } from "./boards";
|
import { boards } from "./boards";
|
||||||
import { cardsToLabels } from "./cards";
|
import { cardsToLabels } from "./cards";
|
||||||
import { imports } from "./imports";
|
import { imports } from "./imports";
|
||||||
import { users } from "./users";
|
import { users } from "./users";
|
||||||
|
|
||||||
export const labels = pgTable(
|
export const labels = pgTable("label", {
|
||||||
"label",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
colourCode: varchar("colourCode", { length: 12 }),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
createdBy: uuid("createdBy")
|
||||||
colourCode: varchar("colourCode", { length: 12 }),
|
.notNull()
|
||||||
createdBy: uuid("createdBy")
|
.references(() => users.id),
|
||||||
.notNull()
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
.references(() => users.id),
|
updatedAt: timestamp("updatedAt"),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
boardId: bigint("boardId", { mode: "number" })
|
||||||
updatedAt: timestamp("updatedAt"),
|
.notNull()
|
||||||
boardId: bigint("boardId", { mode: "number" })
|
.references(() => boards.id, { onDelete: "cascade" }),
|
||||||
.notNull()
|
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||||
.references(() => boards.id, { onDelete: "cascade" }),
|
}).enableRLS();
|
||||||
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 labelsRelations = relations(labels, ({ one, many }) => ({
|
export const labelsRelations = relations(labels, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
|
|||||||
@@ -1,98 +1,36 @@
|
|||||||
import { relations, sql } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
bigint,
|
bigint,
|
||||||
bigserial,
|
bigserial,
|
||||||
integer,
|
integer,
|
||||||
pgPolicy,
|
|
||||||
pgTable,
|
pgTable,
|
||||||
timestamp,
|
timestamp,
|
||||||
uuid,
|
uuid,
|
||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
|
||||||
|
|
||||||
import { boards } from "./boards";
|
import { boards } from "./boards";
|
||||||
import { cards } from "./cards";
|
import { cards } from "./cards";
|
||||||
import { imports } from "./imports";
|
import { imports } from "./imports";
|
||||||
import { users } from "./users";
|
import { users } from "./users";
|
||||||
|
|
||||||
export const lists = pgTable(
|
export const lists = pgTable("list", {
|
||||||
"list",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
index: integer("index").notNull(),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
createdBy: uuid("createdBy")
|
||||||
index: integer("index").notNull(),
|
.notNull()
|
||||||
createdBy: uuid("createdBy")
|
.references(() => users.id),
|
||||||
.notNull()
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
.references(() => users.id),
|
updatedAt: timestamp("updatedAt"),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
deletedAt: timestamp("deletedAt"),
|
||||||
updatedAt: timestamp("updatedAt"),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
deletedAt: timestamp("deletedAt"),
|
boardId: bigint("boardId", { mode: "number" })
|
||||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
.notNull()
|
||||||
boardId: bigint("boardId", { mode: "number" })
|
.references(() => boards.id, { onDelete: "cascade" }),
|
||||||
.notNull()
|
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||||
.references(() => boards.id, { onDelete: "cascade" }),
|
}).enableRLS();
|
||||||
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 listsRelations = relations(lists, ({ one, many }) => ({
|
export const listsRelations = relations(lists, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import { relations, sql } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
|
||||||
pgPolicy,
|
|
||||||
pgTable,
|
|
||||||
timestamp,
|
|
||||||
uuid,
|
|
||||||
varchar,
|
|
||||||
} from "drizzle-orm/pg-core";
|
|
||||||
import { authenticatedRole } from "drizzle-orm/supabase";
|
|
||||||
|
|
||||||
import { boards } from "./boards";
|
import { boards } from "./boards";
|
||||||
import { cards } from "./cards";
|
import { cards } from "./cards";
|
||||||
@@ -14,35 +7,14 @@ import { imports } from "./imports";
|
|||||||
import { lists } from "./lists";
|
import { lists } from "./lists";
|
||||||
import { workspaceMembers, workspaces } from "./workspaces";
|
import { workspaceMembers, workspaces } from "./workspaces";
|
||||||
|
|
||||||
export const users = pgTable(
|
export const users = pgTable("user", {
|
||||||
"user",
|
id: uuid("id").notNull().primaryKey(),
|
||||||
{
|
name: varchar("name", { length: 255 }),
|
||||||
id: uuid("id").notNull().primaryKey(),
|
email: varchar("email", { length: 255 }).notNull().unique(),
|
||||||
name: varchar("name", { length: 255 }),
|
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||||
email: varchar("email", { length: 255 }).notNull().unique(),
|
image: varchar("image", { length: 255 }),
|
||||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
stripeCustomerId: varchar("stripeCustomerId", { length: 255 }),
|
||||||
image: varchar("image", { length: 255 }),
|
}).enableRLS();
|
||||||
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 usersRelations = relations(users, ({ many }) => ({
|
export const usersRelations = relations(users, ({ many }) => ({
|
||||||
boards: many(boards),
|
boards: many(boards),
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { relations, sql } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
bigint,
|
bigint,
|
||||||
bigserial,
|
bigserial,
|
||||||
pgEnum,
|
pgEnum,
|
||||||
pgPolicy,
|
|
||||||
pgTable,
|
pgTable,
|
||||||
text,
|
text,
|
||||||
timestamp,
|
timestamp,
|
||||||
uuid,
|
uuid,
|
||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
|
|
||||||
|
|
||||||
import { users } from "./users";
|
import { users } from "./users";
|
||||||
|
|
||||||
@@ -27,140 +25,44 @@ export const workspacePlanEnum = pgEnum("workspace_plan", [
|
|||||||
"enterprise",
|
"enterprise",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const workspaces = pgTable(
|
export const workspaces = pgTable("workspace", {
|
||||||
"workspace",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
description: text("description"),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||||
description: text("description"),
|
plan: workspacePlanEnum("plan").notNull().default("free"),
|
||||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
createdBy: uuid("createdBy")
|
||||||
plan: workspacePlanEnum("plan").notNull().default("free"),
|
.notNull()
|
||||||
createdBy: uuid("createdBy")
|
.references(() => users.id),
|
||||||
.notNull()
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
.references(() => users.id),
|
updatedAt: timestamp("updatedAt"),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
deletedAt: timestamp("deletedAt"),
|
||||||
updatedAt: timestamp("updatedAt"),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
deletedAt: timestamp("deletedAt"),
|
}).enableRLS();
|
||||||
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 workspaceRelations = relations(workspaces, ({ one, many }) => ({
|
export const workspaceRelations = relations(workspaces, ({ one, many }) => ({
|
||||||
user: one(users, { fields: [workspaces.createdBy], references: [users.id] }),
|
user: one(users, { fields: [workspaces.createdBy], references: [users.id] }),
|
||||||
members: many(workspaceMembers),
|
members: many(workspaceMembers),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const workspaceMembers = pgTable(
|
export const workspaceMembers = pgTable("workspace_members", {
|
||||||
"workspace_members",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
userId: uuid("userId")
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
.notNull()
|
||||||
userId: uuid("userId")
|
.references(() => users.id),
|
||||||
.notNull()
|
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||||
.references(() => users.id),
|
.notNull()
|
||||||
workspaceId: bigint("workspaceId", { mode: "number" })
|
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||||
.notNull()
|
createdBy: uuid("createdBy").notNull(),
|
||||||
.references(() => workspaces.id, { onDelete: "cascade" }),
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
createdBy: uuid("createdBy").notNull(),
|
updatedAt: timestamp("updatedAt"),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
deletedAt: timestamp("deletedAt"),
|
||||||
updatedAt: timestamp("updatedAt"),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
deletedAt: timestamp("deletedAt"),
|
role: memberRoleEnum("role").notNull(),
|
||||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
status: memberStatusEnum("status").default("invited").notNull(),
|
||||||
role: memberRoleEnum("role").notNull(),
|
}).enableRLS();
|
||||||
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 slugs = pgTable("workspace_slugs", {
|
export const slugs = pgTable("workspace_slugs", {
|
||||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||||
|
|||||||
@@ -74,15 +74,15 @@ export function createNextApiClient(req: NextApiRequest) {
|
|||||||
|
|
||||||
export function createTRPCClient(req: Request, resHeaders: Headers) {
|
export function createTRPCClient(req: Request, resHeaders: Headers) {
|
||||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
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");
|
throw new Error("Missing Supabase environment variables");
|
||||||
}
|
}
|
||||||
|
|
||||||
const supabase = createServerClient<Database, "public">(
|
const supabase = createServerClient<Database, "public">(
|
||||||
supabaseUrl,
|
supabaseUrl,
|
||||||
supabaseKey,
|
serviceKey,
|
||||||
{
|
{
|
||||||
cookies: {
|
cookies: {
|
||||||
get(name: string) {
|
get(name: string) {
|
||||||
@@ -101,27 +101,3 @@ export function createTRPCClient(req: Request, resHeaders: Headers) {
|
|||||||
|
|
||||||
return supabase;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@@ -264,11 +264,11 @@ importers:
|
|||||||
specifier: ^0.10.0
|
specifier: ^0.10.0
|
||||||
version: 0.10.0(utf-8-validate@6.0.3)
|
version: 0.10.0(utf-8-validate@6.0.3)
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.36.4
|
specifier: ^0.42.0
|
||||||
version: 0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1)
|
version: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)
|
||||||
drizzle-zod:
|
drizzle-zod:
|
||||||
specifier: ^0.5.1
|
specifier: ^0.5.1
|
||||||
version: 0.5.1(drizzle-orm@0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1))(zod@3.24.0)
|
version: 0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0)
|
||||||
zod:
|
zod:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.24.0
|
version: 3.24.0
|
||||||
@@ -2562,36 +2562,35 @@ packages:
|
|||||||
resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==}
|
resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
drizzle-orm@0.36.4:
|
drizzle-orm@0.42.0:
|
||||||
resolution: {integrity: sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==}
|
resolution: {integrity: sha512-pS8nNJm2kBNZwrOjTHJfdKkaU+KuUQmV/vk5D57NojDq4FG+0uAYGMulXtYT///HfgsMF0hnFFvu1ezI3OwOkg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@aws-sdk/client-rds-data': '>=3'
|
'@aws-sdk/client-rds-data': '>=3'
|
||||||
'@cloudflare/workers-types': '>=3'
|
'@cloudflare/workers-types': '>=4'
|
||||||
'@electric-sql/pglite': '>=0.2.0'
|
'@electric-sql/pglite': '>=0.2.0'
|
||||||
'@libsql/client': '>=0.10.0'
|
'@libsql/client': '>=0.10.0'
|
||||||
'@libsql/client-wasm': '>=0.10.0'
|
'@libsql/client-wasm': '>=0.10.0'
|
||||||
'@neondatabase/serverless': '>=0.10.0'
|
'@neondatabase/serverless': '>=0.10.0'
|
||||||
'@op-engineering/op-sqlite': '>=2'
|
'@op-engineering/op-sqlite': '>=2'
|
||||||
'@opentelemetry/api': ^1.4.1
|
'@opentelemetry/api': ^1.4.1
|
||||||
'@planetscale/database': '>=1'
|
'@planetscale/database': '>=1.13'
|
||||||
'@prisma/client': '*'
|
'@prisma/client': '*'
|
||||||
'@tidbcloud/serverless': '*'
|
'@tidbcloud/serverless': '*'
|
||||||
'@types/better-sqlite3': '*'
|
'@types/better-sqlite3': '*'
|
||||||
'@types/pg': '*'
|
'@types/pg': '*'
|
||||||
'@types/react': '>=18'
|
|
||||||
'@types/sql.js': '*'
|
'@types/sql.js': '*'
|
||||||
'@vercel/postgres': '>=0.8.0'
|
'@vercel/postgres': '>=0.8.0'
|
||||||
'@xata.io/client': '*'
|
'@xata.io/client': '*'
|
||||||
better-sqlite3: '>=7'
|
better-sqlite3: '>=7'
|
||||||
bun-types: '*'
|
bun-types: '*'
|
||||||
expo-sqlite: '>=14.0.0'
|
expo-sqlite: '>=14.0.0'
|
||||||
|
gel: '>=2'
|
||||||
knex: '*'
|
knex: '*'
|
||||||
kysely: '*'
|
kysely: '*'
|
||||||
mysql2: '>=2'
|
mysql2: '>=2'
|
||||||
pg: '>=8'
|
pg: '>=8'
|
||||||
postgres: '>=3'
|
postgres: '>=3'
|
||||||
prisma: '*'
|
prisma: '*'
|
||||||
react: '>=18'
|
|
||||||
sql.js: '>=1'
|
sql.js: '>=1'
|
||||||
sqlite3: '>=5'
|
sqlite3: '>=5'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -2621,8 +2620,6 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
'@types/pg':
|
'@types/pg':
|
||||||
optional: true
|
optional: true
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
'@types/sql.js':
|
'@types/sql.js':
|
||||||
optional: true
|
optional: true
|
||||||
'@vercel/postgres':
|
'@vercel/postgres':
|
||||||
@@ -2635,6 +2632,8 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
expo-sqlite:
|
expo-sqlite:
|
||||||
optional: true
|
optional: true
|
||||||
|
gel:
|
||||||
|
optional: true
|
||||||
knex:
|
knex:
|
||||||
optional: true
|
optional: true
|
||||||
kysely:
|
kysely:
|
||||||
@@ -2647,8 +2646,6 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
prisma:
|
prisma:
|
||||||
optional: true
|
optional: true
|
||||||
react:
|
|
||||||
optional: true
|
|
||||||
sql.js:
|
sql.js:
|
||||||
optional: true
|
optional: true
|
||||||
sqlite3:
|
sqlite3:
|
||||||
@@ -7231,19 +7228,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
drizzle-orm@0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1):
|
drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@neondatabase/serverless': 0.10.4
|
'@neondatabase/serverless': 0.10.4
|
||||||
'@types/pg': 8.11.6
|
'@types/pg': 8.11.6
|
||||||
'@types/react': 18.3.15
|
|
||||||
'@vercel/postgres': 0.10.0(utf-8-validate@6.0.3)
|
'@vercel/postgres': 0.10.0(utf-8-validate@6.0.3)
|
||||||
pg: 8.13.1
|
pg: 8.13.1
|
||||||
postgres: 3.4.5
|
postgres: 3.4.5
|
||||||
react: 18.3.1
|
|
||||||
|
|
||||||
drizzle-zod@0.5.1(drizzle-orm@0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1))(zod@3.24.0):
|
drizzle-zod@0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
drizzle-orm: 0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1)
|
drizzle-orm: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)
|
||||||
zod: 3.24.0
|
zod: 3.24.0
|
||||||
|
|
||||||
dunder-proto@1.0.0:
|
dunder-proto@1.0.0:
|
||||||
|
|||||||
Reference in New Issue
Block a user