chore: move policies to drizzle

This commit is contained in:
Henry
2025-04-17 17:22:53 +01:00
parent 297408c1fe
commit 0c25fe3864
16 changed files with 3554 additions and 1086 deletions

View File

@@ -0,0 +1,127 @@
import { relations, sql } from "drizzle-orm";
import {
bigint,
bigserial,
index,
pgEnum,
pgPolicy,
pgTable,
text,
timestamp,
uniqueIndex,
uuid,
varchar,
} from "drizzle-orm/pg-core";
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
import { imports } from "./imports";
import { labels } from "./labels";
import { lists } from "./lists";
import { users } from "./users";
import { workspaces } from "./workspaces";
export const boardVisibilityEnum = pgEnum("board_visibility", [
"private",
"public",
]);
export const boards = pgTable(
"board",
{
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(),
createdBy: uuid("createdBy")
.notNull()
.references(() => users.id),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt"),
deletedAt: timestamp("deletedAt"),
deletedBy: uuid("deletedBy").references(() => users.id),
importId: bigint("importId", { mode: "number" }).references(
() => imports.id,
),
workspaceId: bigint("workspaceId", { mode: "number" })
.notNull()
.references(() => workspaces.id, { onDelete: "cascade" }),
visibility: boardVisibilityEnum("visibility").notNull().default("private"),
},
(table) => [
index("board_visibility_idx").on(table.visibility),
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();
export const boardsRelations = relations(boards, ({ one, many }) => ({
createdBy: one(users, {
fields: [boards.createdBy],
references: [users.id],
}),
lists: many(lists),
labels: many(labels),
deletedBy: one(users, {
fields: [boards.deletedBy],
references: [users.id],
}),
import: one(imports, {
fields: [boards.importId],
references: [imports.id],
}),
workspace: one(workspaces, {
fields: [boards.workspaceId],
references: [workspaces.id],
}),
}));

View File

@@ -0,0 +1,504 @@
import { relations, sql } from "drizzle-orm";
import {
bigint,
bigserial,
integer,
pgEnum,
pgPolicy,
pgTable,
primaryKey,
text,
timestamp,
uuid,
varchar,
} from "drizzle-orm/pg-core";
import { anonRole, authenticatedRole } from "drizzle-orm/supabase";
import { imports } from "./imports";
import { labels } from "./labels";
import { lists } from "./lists";
import { users } from "./users";
import { workspaceMembers } from "./workspaces";
export const activityTypeEnum = pgEnum("card_activity_type", [
"card.created",
"card.updated.title",
"card.updated.description",
"card.updated.index",
"card.updated.list",
"card.updated.label.added",
"card.updated.label.removed",
"card.updated.member.added",
"card.updated.member.removed",
"card.updated.comment.added",
"card.updated.comment.updated",
"card.updated.comment.deleted",
"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 cardsRelations = relations(cards, ({ one, many }) => ({
createdBy: one(users, {
fields: [cards.createdBy],
references: [users.id],
}),
list: one(lists, {
fields: [cards.listId],
references: [lists.id],
}),
deletedBy: one(users, {
fields: [cards.deletedBy],
references: [users.id],
}),
labels: many(cardsToLabels),
members: many(cardToWorkspaceMembers),
import: one(imports, {
fields: [cards.importId],
references: [imports.id],
}),
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 cardActivitiesRelations = relations(cardActivities, ({ one }) => ({
card: one(cards, {
fields: [cardActivities.cardId],
references: [cards.id],
}),
fromList: one(lists, {
fields: [cardActivities.fromListId],
references: [lists.id],
}),
toList: one(lists, {
fields: [cardActivities.toListId],
references: [lists.id],
}),
label: one(labels, {
fields: [cardActivities.labelId],
references: [labels.id],
}),
workspaceMember: one(workspaceMembers, {
fields: [cardActivities.workspaceMemberId],
references: [workspaceMembers.id],
}),
createdBy: one(users, {
fields: [cardActivities.createdBy],
references: [users.id],
}),
}));
export const cardsToLabels = pgTable(
"_card_labels",
{
cardId: bigint("cardId", { mode: "number" })
.notNull()
.references(() => cards.id),
labelId: bigint("labelId", { mode: "number" })
.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()
)
`,
}),
],
).enableRLS();
export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({
card: one(cards, {
fields: [cardsToLabels.cardId],
references: [cards.id],
}),
label: one(labels, {
fields: [cardsToLabels.labelId],
references: [labels.id],
}),
}));
export const cardToWorkspaceMembers = pgTable(
"_card_workspace_members",
{
cardId: bigint("cardId", { mode: "number" })
.notNull()
.references(() => cards.id),
workspaceMemberId: bigint("workspaceMemberId", { mode: "number" })
.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()
)
)
`,
}),
],
).enableRLS();
export const cardToWorkspaceMembersRelations = relations(
cardToWorkspaceMembers,
({ one }) => ({
card: one(cards, {
fields: [cardToWorkspaceMembers.cardId],
references: [cards.id],
}),
member: one(workspaceMembers, {
fields: [cardToWorkspaceMembers.workspaceMemberId],
references: [workspaceMembers.id],
}),
}),
);
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 commentsRelations = relations(comments, ({ one }) => ({
card: one(cards, {
fields: [comments.cardId],
references: [cards.id],
}),
createdBy: one(users, {
fields: [comments.createdBy],
references: [users.id],
}),
deletedBy: one(users, {
fields: [comments.deletedBy],
references: [users.id],
}),
}));

View File

@@ -0,0 +1,30 @@
import { relations } from "drizzle-orm";
import {
bigserial,
boolean,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { users } from "./users";
export const feedback = pgTable("feedback", {
id: bigserial("id", { mode: "number" }).primaryKey(),
feedback: text("feedback").notNull(),
createdBy: uuid("createdBy")
.notNull()
.references(() => users.id),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt"),
url: text("url").notNull(),
reviewed: boolean("reviewed").default(false).notNull(),
}).enableRLS();
export const feedbackRelations = relations(feedback, ({ one }) => ({
createdBy: one(users, {
fields: [feedback.createdBy],
references: [users.id],
}),
}));

View File

@@ -0,0 +1,59 @@
import { relations, sql } 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";
import { labels } from "./labels";
import { lists } from "./lists";
import { users } from "./users";
export const importSourceEnum = pgEnum("source", ["trello"]);
export const importStatusEnum = pgEnum("status", [
"started",
"success",
"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 importsRelations = relations(imports, ({ one, many }) => ({
createdBy: one(users, {
fields: [imports.createdBy],
references: [users.id],
}),
boards: many(boards),
cards: many(cards),
lists: many(lists),
labels: many(labels),
}));

View File

@@ -0,0 +1,8 @@
export * from "./boards";
export * from "./cards";
export * from "./feedback";
export * from "./imports";
export * from "./labels";
export * from "./lists";
export * from "./users";
export * from "./workspaces";

View File

@@ -0,0 +1,108 @@
import { relations, sql } 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 labelsRelations = relations(labels, ({ one, many }) => ({
createdBy: one(users, {
fields: [labels.createdBy],
references: [users.id],
}),
board: one(boards, {
fields: [labels.boardId],
references: [boards.id],
}),
cards: many(cardsToLabels),
import: one(imports, {
fields: [labels.importId],
references: [imports.id],
}),
}));

View File

@@ -0,0 +1,115 @@
import { relations, sql } 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 listsRelations = relations(lists, ({ one, many }) => ({
createdBy: one(users, {
fields: [lists.createdBy],
references: [users.id],
}),
board: one(boards, {
fields: [lists.boardId],
references: [boards.id],
}),
cards: many(cards),
deletedBy: one(users, {
fields: [lists.deletedBy],
references: [users.id],
}),
import: one(imports, {
fields: [lists.importId],
references: [imports.id],
}),
}));

View File

@@ -0,0 +1,75 @@
import { relations, sql } from "drizzle-orm";
import {
pgPolicy,
pgTable,
timestamp,
uuid,
varchar,
} from "drizzle-orm/pg-core";
import { authenticatedRole } from "drizzle-orm/supabase";
import { boards } from "./boards";
import { cards } from "./cards";
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 usersRelations = relations(users, ({ many }) => ({
boards: many(boards),
cards: many(cards),
imports: many(imports),
lists: many(lists),
workspaces: many(workspaces),
}));
export const usersToWorkspacesRelations = relations(
workspaceMembers,
({ one }) => ({
addedBy: one(users, {
fields: [workspaceMembers.createdBy],
references: [users.id],
}),
deletedBy: one(users, {
fields: [workspaceMembers.deletedBy],
references: [users.id],
}),
user: one(users, {
fields: [workspaceMembers.userId],
references: [users.id],
}),
workspace: one(workspaces, {
fields: [workspaceMembers.workspaceId],
references: [workspaces.id],
}),
}),
);

View File

@@ -0,0 +1,168 @@
import { relations, sql } 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";
export const memberRoleEnum = pgEnum("role", ["admin", "member", "guest"]);
export const memberStatusEnum = pgEnum("member_status", [
"invited",
"active",
"removed",
]);
export const slugTypeEnum = pgEnum("slug_type", ["reserved", "premium"]);
export const workspacePlanEnum = pgEnum("workspace_plan", [
"free",
"pro",
"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 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 slugs = pgTable("workspace_slugs", {
slug: varchar("slug", { length: 255 }).notNull().unique(),
type: slugTypeEnum("type").notNull(),
});