diff --git a/apps/web/src/pages/api/auth/confirm.ts b/apps/web/src/pages/api/auth/confirm.ts
deleted file mode 100644
index e5b134a0..00000000
--- a/apps/web/src/pages/api/auth/confirm.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-// import type { EmailOtpType } from "@supabase/supabase-js";
-// import type { NextApiRequest } from "next";
-// import { NextResponse } from "next/server";
-
-// import { createDrizzleClient } from "@kan/db/client";
-// import * as memberRepo from "@kan/db/repository/member.repo";
-// import * as userRepo from "@kan/db/repository/user.repo";
-// import { stripe } from "@kan/stripe";
-// import { createNextApiClient } from "@kan/";
-
-// export default async function handler(req: NextApiRequest) {
-// if (req.method !== "GET") {
-// return new NextResponse(null, {
-// status: 405,
-// headers: { Allow: "GET" },
-// });
-// }
-
-// if (!req.url) {
-// return new NextResponse(null, {
-// status: 400,
-// });
-// }
-
-// const url = new URL(req.url);
-// const queryParams = Object.fromEntries(url.searchParams.entries());
-
-// const tokenHash = queryParams.token_hash;
-// const type = queryParams.type;
-// const code = queryParams.code;
-// const memberPublicId = queryParams.memberPublicId;
-
-// let next = "/error";
-
-// let authRes;
-
-// const response = NextResponse.next();
-
-// if ((tokenHash && type) ?? code) {
-// const supabaseClient = createNextClient(req, response);
-
-// if (tokenHash && type) {
-// authRes = await supabaseClient.auth.verifyOtp({
-// type: type as EmailOtpType,
-// token_hash: tokenHash,
-// });
-// }
-
-// if (code) {
-// authRes = await supabaseClient.auth.exchangeCodeForSession(code);
-// }
-
-// const user = authRes?.data.user;
-
-// const db = createDrizzleClient();
-
-// if (user?.id && user.email) {
-// const existingUser = await userRepo.getById(db, user.id);
-
-// if (!existingUser) {
-// const stripeCustomer = await stripe.customers.create({
-// email: user.email,
-// metadata: {
-// userId: user.id,
-// },
-// });
-
-// await userRepo.create(db, {
-// id: user.id,
-// email: user.email,
-// stripeCustomerId: stripeCustomer.id,
-// });
-// }
-// }
-
-// if (memberPublicId) {
-// const member = await memberRepo.getByPublicId(db, memberPublicId);
-
-// if (member?.id) {
-// await memberRepo.acceptInvite(db, member.id);
-// }
-// }
-
-// if (authRes?.error) {
-// console.error(authRes.error);
-// } else {
-// next = queryParams.next ?? "/boards";
-// }
-// }
-
-// const redirectResponse = NextResponse.redirect(new URL(next, req.url));
-
-// response.headers.getSetCookie().forEach((cookie) => {
-// redirectResponse.headers.append("Set-Cookie", cookie);
-// });
-
-// return redirectResponse;
-// }
diff --git a/apps/web/src/pages/api/trpc/[trpc].ts b/apps/web/src/pages/api/trpc/[trpc].ts
index 5b900cb3..8403e9cb 100644
--- a/apps/web/src/pages/api/trpc/[trpc].ts
+++ b/apps/web/src/pages/api/trpc/[trpc].ts
@@ -1,4 +1,4 @@
-import { type NextApiRequest, type NextApiResponse } from "next";
+import type { NextApiRequest, NextApiResponse } from "next";
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { appRouter } from "@kan/api/root";
@@ -17,10 +17,7 @@ const nextApiHandler = createNextApiHandler({
: undefined,
});
-export default async function handler(
- req: NextApiRequest,
- res: NextApiResponse,
-) {
+export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "OPTIONS") {
res.writeHead(200);
return res.end();
diff --git a/apps/web/src/views/members/index.tsx b/apps/web/src/views/members/index.tsx
index 724c29f4..07904967 100644
--- a/apps/web/src/views/members/index.tsx
+++ b/apps/web/src/views/members/index.tsx
@@ -10,11 +10,7 @@ import { PageHead } from "~/components/PageHead";
import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/providers/workspace";
import { api } from "~/utils/api";
-import {
- getAvatarUrl,
- getInitialsFromName,
- inferInitialsFromEmail,
-} from "~/utils/helpers";
+import { getAvatarUrl } from "~/utils/helpers";
import { DeleteMemberConfirmation } from "./components/DeleteMemberConfirmation";
import { InviteMemberForm } from "./components/InviteMemberForm";
@@ -46,10 +42,6 @@ export default function MembersPage() {
isLastRow?: boolean;
showSkeleton?: boolean;
}) => {
- const initials = memberName
- ? getInitialsFromName(memberName)
- : inferInitialsFromEmail(memberEmail ?? "");
-
return (
|
@@ -192,9 +184,9 @@ export default function MembersPage() {
member.user.email === input.email,
+ (member) => member.email === input.email,
);
if (isInvitedEmailAlreadyMember) {
@@ -59,79 +58,12 @@ export const memberRouter = createTRPCRouter({
});
}
- let invitedUserId: string | undefined;
- let hashedToken: string | undefined;
- let verificationType: string | undefined;
-
const existingUser = await userRepo.getByEmail(ctx.db, input.email);
- // if (existingUser) {
- // invitedUserId = existingUser.id;
-
- // const magicLink = await ctx.supabaseClient.auth.admin.generateLink({
- // type: "magiclink",
- // email: input.email,
- // options: {
- // redirectTo: process.env.WEBSITE_URL,
- // },
- // });
-
- // hashedToken = magicLink.data.properties?.hashed_token;
- // verificationType = magicLink.data.properties?.verification_type;
- // } else {
- // const invite = await ctx.supabaseClient.auth.admin.generateLink({
- // type: "invite",
- // email: input.email,
- // options: {
- // redirectTo: process.env.WEBSITE_URL,
- // },
- // });
-
- // hashedToken = invite.data.properties?.hashed_token;
- // verificationType = invite.data.properties?.verification_type;
-
- // const invitedUserAuthId = invite.data.user?.id;
- // const invitedUserEmail = invite.data.user?.email;
-
- // if (invitedUserAuthId && invitedUserEmail) {
- // const stripeCustomer = await stripe.customers.create({
- // email: invitedUserEmail,
- // metadata: {
- // userId: invitedUserAuthId,
- // },
- // });
-
- // const newUser = await userRepo.create(ctx.db, {
- // email: invitedUserEmail,
- // id: invitedUserAuthId,
- // stripeCustomerId: stripeCustomer.id,
- // });
-
- // if (!newUser)
- // throw new TRPCError({
- // message: `Failed to create a new user for email ${invitedUserEmail}`,
- // code: "INTERNAL_SERVER_ERROR",
- // });
-
- // invitedUserId = newUser.id;
- // }
- // }
-
- if (!invitedUserId)
- throw new TRPCError({
- message: `Unable to invite user with email ${input.email}`,
- code: "INTERNAL_SERVER_ERROR",
- });
-
- if (!hashedToken || !verificationType)
- throw new TRPCError({
- message: `Unable to generate magic link for user with email ${input.email}`,
- code: "INTERNAL_SERVER_ERROR",
- });
-
const invite = await memberRepo.create(ctx.db, {
workspaceId: workspace.id,
- userId: invitedUserId,
+ email: input.email,
+ userId: existingUser?.id ?? null,
createdBy: userId,
role: "member",
status: "invited",
@@ -143,16 +75,16 @@ export const memberRouter = createTRPCRouter({
code: "INTERNAL_SERVER_ERROR",
});
- const magicLoginUrl = `${process.env.WEBSITE_URL}/api/auth/confirm?token_hash=${hashedToken}&type=${verificationType}&memberPublicId=${invite.publicId}`;
+ const { error } = await authClient.signIn.magicLink({
+ email: input.email,
+ callbackURL: `/boards?type=invite&memberPublicId=${invite.publicId}`,
+ });
- await sendEmail(
- input.email,
- "Invitation to join workspace",
- "JOIN_WORKSPACE",
- {
- magicLoginUrl,
- },
- );
+ if (error)
+ throw new TRPCError({
+ message: `Failed to send magic link to user with email ${input.email}`,
+ code: "INTERNAL_SERVER_ERROR",
+ });
return invite;
}),
diff --git a/packages/api/src/routers/user.ts b/packages/api/src/routers/user.ts
index 51b83b6d..2052cf91 100644
--- a/packages/api/src/routers/user.ts
+++ b/packages/api/src/routers/user.ts
@@ -39,7 +39,7 @@ export const userRouter = createTRPCRouter({
const result = await userRepo.getById(ctx.db, userId);
- if (!result?.name) {
+ if (!result) {
throw new TRPCError({
message: `User not found`,
code: "NOT_FOUND",
diff --git a/packages/api/src/routers/workspace.ts b/packages/api/src/routers/workspace.ts
index dc37083b..930ba67f 100644
--- a/packages/api/src/routers/workspace.ts
+++ b/packages/api/src/routers/workspace.ts
@@ -117,8 +117,9 @@ export const workspaceRouter = createTRPCRouter({
.output(z.custom>>())
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
+ const userEmail = ctx.user?.email;
- if (!userId)
+ if (!userId || !userEmail)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
@@ -131,6 +132,7 @@ export const workspaceRouter = createTRPCRouter({
name: input.name,
slug: workspacePublicId,
createdBy: userId,
+ createdByEmail: userEmail,
});
if (!result.publicId)
diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts
index 58364575..6c553e52 100644
--- a/packages/auth/src/auth.ts
+++ b/packages/auth/src/auth.ts
@@ -5,6 +5,7 @@ import { apiKey } from "better-auth/plugins";
import { magicLink } from "better-auth/plugins/magic-link";
import type { dbClient } from "@kan/db/client";
+import * as memberRepo from "@kan/db/repository/member.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import * as schema from "@kan/db/schema";
import { sendEmail } from "@kan/email";
@@ -43,35 +44,69 @@ export const initAuth = (db: dbClient) => {
plugins: [
apiKey(),
magicLink({
+ expiresIn: 60 * 60 * 24 * 7, // 7 days
sendMagicLink: async ({ email, url }) => {
- await sendEmail(email, "Sign in to kan.bn", "MAGIC_LINK", {
- magicLoginUrl: url,
- });
+ if (url.includes("type=invite")) {
+ await sendEmail(
+ email,
+ "Invitation to join workspace",
+ "JOIN_WORKSPACE",
+ {
+ magicLoginUrl: url,
+ },
+ );
+ } else {
+ await sendEmail(email, "Sign in to kan.bn", "MAGIC_LINK", {
+ magicLoginUrl: url,
+ });
+ }
},
}),
],
hooks: {
- // after: createAuthMiddleware(async (ctx) => {
- // if (ctx.path.startsWith("/sign-up") || ctx.path.startsWith("/sign-in")) {
- // const session = ctx.context.session;
- // if (
- // session &&
- // process.env.NEXT_PUBLIC_KAN_ENV === "cloud" &&
- // !session.user.stripeCustomerId
- // ) {
- // const stripe = createStripeClient();
- // const stripeCustomer = await stripe.customers.create({
- // email: session.user.email,
- // metadata: {
- // userId: session.user.id,
- // },
- // });
- // await userRepo.update(db, session.user.id, {
- // stripeCustomerId: stripeCustomer.id,
- // });
- // }
- // }
- // }),
+ after: createAuthMiddleware(async (ctx) => {
+ if (ctx.path.startsWith("/get-session")) {
+ const user = ctx.context.session?.user;
+
+ if (
+ process.env.NEXT_PUBLIC_KAN_ENV === "cloud" &&
+ user &&
+ !user.stripeCustomerId
+ ) {
+ const stripe = createStripeClient();
+ const stripeCustomer = await stripe.customers.create({
+ email: user.email,
+ metadata: {
+ userId: user.id,
+ },
+ });
+
+ await userRepo.update(db, user.id, {
+ stripeCustomerId: stripeCustomer.id,
+ });
+ }
+ } else if (
+ ctx.path === "/magic-link/verify" &&
+ (ctx.query?.callbackURL as string | undefined)?.includes(
+ "type=invite",
+ ) &&
+ ctx.query?.memberPublicId
+ ) {
+ const userId = ctx.context.newSession?.session.userId;
+ const memberPublicId = ctx.query.memberPublicId as string;
+
+ if (userId) {
+ const member = await memberRepo.getByPublicId(db, memberPublicId);
+
+ if (member?.id) {
+ await memberRepo.acceptInvite(db, {
+ memberId: member.id,
+ userId,
+ });
+ }
+ }
+ }
+ }),
},
advanced: {
cookiePrefix: "kan",
diff --git a/packages/db/migrations/20250522083748_AddEmailToWorkspaceMembers.sql b/packages/db/migrations/20250522083748_AddEmailToWorkspaceMembers.sql
new file mode 100644
index 00000000..70cc0573
--- /dev/null
+++ b/packages/db/migrations/20250522083748_AddEmailToWorkspaceMembers.sql
@@ -0,0 +1,4 @@
+ALTER TABLE "workspace_members" ALTER COLUMN "userId" DROP NOT NULL;--> statement-breakpoint
+ALTER TABLE "workspace_members" ADD COLUMN "email" varchar(255);--> statement-breakpoint
+UPDATE "workspace_members" wm SET "email" = u."email" FROM "user" u WHERE wm."userId" = u."id";--> statement-breakpoint
+ALTER TABLE "workspace_members" ALTER COLUMN "email" SET NOT NULL;
\ No newline at end of file
diff --git a/packages/db/migrations/meta/20250522083748_snapshot.json b/packages/db/migrations/meta/20250522083748_snapshot.json
new file mode 100644
index 00000000..6807db47
--- /dev/null
+++ b/packages/db/migrations/meta/20250522083748_snapshot.json
@@ -0,0 +1,2062 @@
+{
+ "id": "8796327d-d47b-41d9-91be-933b6d3cdf55",
+ "prevId": "601a411c-27ca-4b1c-b0bf-983ffbb119f9",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "accountId": {
+ "name": "accountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerId": {
+ "name": "providerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "accessToken": {
+ "name": "accessToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "idToken": {
+ "name": "idToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accessTokenExpiresAt": {
+ "name": "accessTokenExpiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshTokenExpiresAt": {
+ "name": "refreshTokenExpiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.apiKey": {
+ "name": "apiKey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start": {
+ "name": "start",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refillInterval": {
+ "name": "refillInterval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refillAmount": {
+ "name": "refillAmount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lastRefillAt": {
+ "name": "lastRefillAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rateLimitEnabled": {
+ "name": "rateLimitEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rateLimitTimeWindow": {
+ "name": "rateLimitTimeWindow",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rateLimitMax": {
+ "name": "rateLimitMax",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requestCount": {
+ "name": "requestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "remaining": {
+ "name": "remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lastRequest": {
+ "name": "lastRequest",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userAgent": {
+ "name": "userAgent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.board": {
+ "name": "board",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "importId": {
+ "name": "importId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspaceId": {
+ "name": "workspaceId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "board_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'private'"
+ }
+ },
+ "indexes": {
+ "board_visibility_idx": {
+ "name": "board_visibility_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "unique_slug_per_workspace": {
+ "name": "unique_slug_per_workspace",
+ "columns": [
+ {
+ "expression": "workspaceId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"board\".\"deletedAt\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "board_createdBy_user_id_fk": {
+ "name": "board_createdBy_user_id_fk",
+ "tableFrom": "board",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "board_deletedBy_user_id_fk": {
+ "name": "board_deletedBy_user_id_fk",
+ "tableFrom": "board",
+ "tableTo": "user",
+ "columnsFrom": [
+ "deletedBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "board_importId_import_id_fk": {
+ "name": "board_importId_import_id_fk",
+ "tableFrom": "board",
+ "tableTo": "import",
+ "columnsFrom": [
+ "importId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "board_workspaceId_workspace_id_fk": {
+ "name": "board_workspaceId_workspace_id_fk",
+ "tableFrom": "board",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspaceId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "board_publicId_unique": {
+ "name": "board_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.card_activity": {
+ "name": "card_activity",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "card_activity_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cardId": {
+ "name": "cardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromIndex": {
+ "name": "fromIndex",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "toIndex": {
+ "name": "toIndex",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fromListId": {
+ "name": "fromListId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "toListId": {
+ "name": "toListId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelId": {
+ "name": "labelId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspaceMemberId": {
+ "name": "workspaceMemberId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fromTitle": {
+ "name": "fromTitle",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "toTitle": {
+ "name": "toTitle",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fromDescription": {
+ "name": "fromDescription",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "toDescription": {
+ "name": "toDescription",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "commentId": {
+ "name": "commentId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fromComment": {
+ "name": "fromComment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "toComment": {
+ "name": "toComment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "card_activity_cardId_card_id_fk": {
+ "name": "card_activity_cardId_card_id_fk",
+ "tableFrom": "card_activity",
+ "tableTo": "card",
+ "columnsFrom": [
+ "cardId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "card_activity_fromListId_list_id_fk": {
+ "name": "card_activity_fromListId_list_id_fk",
+ "tableFrom": "card_activity",
+ "tableTo": "list",
+ "columnsFrom": [
+ "fromListId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_activity_toListId_list_id_fk": {
+ "name": "card_activity_toListId_list_id_fk",
+ "tableFrom": "card_activity",
+ "tableTo": "list",
+ "columnsFrom": [
+ "toListId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_activity_labelId_label_id_fk": {
+ "name": "card_activity_labelId_label_id_fk",
+ "tableFrom": "card_activity",
+ "tableTo": "label",
+ "columnsFrom": [
+ "labelId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_activity_workspaceMemberId_workspace_members_id_fk": {
+ "name": "card_activity_workspaceMemberId_workspace_members_id_fk",
+ "tableFrom": "card_activity",
+ "tableTo": "workspace_members",
+ "columnsFrom": [
+ "workspaceMemberId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_activity_createdBy_user_id_fk": {
+ "name": "card_activity_createdBy_user_id_fk",
+ "tableFrom": "card_activity",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_activity_commentId_card_comments_id_fk": {
+ "name": "card_activity_commentId_card_comments_id_fk",
+ "tableFrom": "card_activity",
+ "tableTo": "card_comments",
+ "columnsFrom": [
+ "commentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "card_activity_publicId_unique": {
+ "name": "card_activity_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public._card_workspace_members": {
+ "name": "_card_workspace_members",
+ "schema": "",
+ "columns": {
+ "cardId": {
+ "name": "cardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspaceMemberId": {
+ "name": "workspaceMemberId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "_card_workspace_members_cardId_card_id_fk": {
+ "name": "_card_workspace_members_cardId_card_id_fk",
+ "tableFrom": "_card_workspace_members",
+ "tableTo": "card",
+ "columnsFrom": [
+ "cardId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "_card_workspace_members_workspaceMemberId_workspace_members_id_fk": {
+ "name": "_card_workspace_members_workspaceMemberId_workspace_members_id_fk",
+ "tableFrom": "_card_workspace_members",
+ "tableTo": "workspace_members",
+ "columnsFrom": [
+ "workspaceMemberId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "_card_workspace_members_cardId_workspaceMemberId_pk": {
+ "name": "_card_workspace_members_cardId_workspaceMemberId_pk",
+ "columns": [
+ "cardId",
+ "workspaceMemberId"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.card": {
+ "name": "card",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "index": {
+ "name": "index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "importId": {
+ "name": "importId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "card_createdBy_user_id_fk": {
+ "name": "card_createdBy_user_id_fk",
+ "tableFrom": "card",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_deletedBy_user_id_fk": {
+ "name": "card_deletedBy_user_id_fk",
+ "tableFrom": "card",
+ "tableTo": "user",
+ "columnsFrom": [
+ "deletedBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_listId_list_id_fk": {
+ "name": "card_listId_list_id_fk",
+ "tableFrom": "card",
+ "tableTo": "list",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "card_importId_import_id_fk": {
+ "name": "card_importId_import_id_fk",
+ "tableFrom": "card",
+ "tableTo": "import",
+ "columnsFrom": [
+ "importId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "card_publicId_unique": {
+ "name": "card_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public._card_labels": {
+ "name": "_card_labels",
+ "schema": "",
+ "columns": {
+ "cardId": {
+ "name": "cardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "labelId": {
+ "name": "labelId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "_card_labels_cardId_card_id_fk": {
+ "name": "_card_labels_cardId_card_id_fk",
+ "tableFrom": "_card_labels",
+ "tableTo": "card",
+ "columnsFrom": [
+ "cardId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "_card_labels_labelId_label_id_fk": {
+ "name": "_card_labels_labelId_label_id_fk",
+ "tableFrom": "_card_labels",
+ "tableTo": "label",
+ "columnsFrom": [
+ "labelId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "_card_labels_cardId_labelId_pk": {
+ "name": "_card_labels_cardId_labelId_pk",
+ "columns": [
+ "cardId",
+ "labelId"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.card_comments": {
+ "name": "card_comments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cardId": {
+ "name": "cardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "card_comments_cardId_card_id_fk": {
+ "name": "card_comments_cardId_card_id_fk",
+ "tableFrom": "card_comments",
+ "tableTo": "card",
+ "columnsFrom": [
+ "cardId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "card_comments_createdBy_user_id_fk": {
+ "name": "card_comments_createdBy_user_id_fk",
+ "tableFrom": "card_comments",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_comments_deletedBy_user_id_fk": {
+ "name": "card_comments_deletedBy_user_id_fk",
+ "tableFrom": "card_comments",
+ "tableTo": "user",
+ "columnsFrom": [
+ "deletedBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "card_comments_publicId_unique": {
+ "name": "card_comments_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.feedback": {
+ "name": "feedback",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "feedback": {
+ "name": "feedback",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reviewed": {
+ "name": "reviewed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "feedback_createdBy_user_id_fk": {
+ "name": "feedback_createdBy_user_id_fk",
+ "tableFrom": "feedback",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.import": {
+ "name": "import",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "import_createdBy_user_id_fk": {
+ "name": "import_createdBy_user_id_fk",
+ "tableFrom": "import",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "import_publicId_unique": {
+ "name": "import_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.label": {
+ "name": "label",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "colourCode": {
+ "name": "colourCode",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boardId": {
+ "name": "boardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "importId": {
+ "name": "importId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "label_createdBy_user_id_fk": {
+ "name": "label_createdBy_user_id_fk",
+ "tableFrom": "label",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "label_boardId_board_id_fk": {
+ "name": "label_boardId_board_id_fk",
+ "tableFrom": "label",
+ "tableTo": "board",
+ "columnsFrom": [
+ "boardId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "label_importId_import_id_fk": {
+ "name": "label_importId_import_id_fk",
+ "tableFrom": "label",
+ "tableTo": "import",
+ "columnsFrom": [
+ "importId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "label_publicId_unique": {
+ "name": "label_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.list": {
+ "name": "list",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "index": {
+ "name": "index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boardId": {
+ "name": "boardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "importId": {
+ "name": "importId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "list_createdBy_user_id_fk": {
+ "name": "list_createdBy_user_id_fk",
+ "tableFrom": "list",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "list_deletedBy_user_id_fk": {
+ "name": "list_deletedBy_user_id_fk",
+ "tableFrom": "list",
+ "tableTo": "user",
+ "columnsFrom": [
+ "deletedBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "list_boardId_board_id_fk": {
+ "name": "list_boardId_board_id_fk",
+ "tableFrom": "list",
+ "tableTo": "board",
+ "columnsFrom": [
+ "boardId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "list_importId_import_id_fk": {
+ "name": "list_importId_import_id_fk",
+ "tableFrom": "list",
+ "tableTo": "import",
+ "columnsFrom": [
+ "importId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "list_publicId_unique": {
+ "name": "list_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "uuid_generate_v4()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "stripeCustomerId": {
+ "name": "stripeCustomerId",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.workspace_slugs": {
+ "name": "workspace_slugs",
+ "schema": "",
+ "columns": {
+ "slug": {
+ "name": "slug",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "slug_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_slugs_slug_unique": {
+ "name": "workspace_slugs_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_members": {
+ "name": "workspace_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspaceId": {
+ "name": "workspaceId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "member_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'invited'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_members_userId_user_id_fk": {
+ "name": "workspace_members_userId_user_id_fk",
+ "tableFrom": "workspace_members",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "workspace_members_workspaceId_workspace_id_fk": {
+ "name": "workspace_members_workspaceId_workspace_id_fk",
+ "tableFrom": "workspace_members",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspaceId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_members_deletedBy_user_id_fk": {
+ "name": "workspace_members_deletedBy_user_id_fk",
+ "tableFrom": "workspace_members",
+ "tableTo": "user",
+ "columnsFrom": [
+ "deletedBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_members_publicId_unique": {
+ "name": "workspace_members_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.workspace": {
+ "name": "workspace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "plan": {
+ "name": "plan",
+ "type": "workspace_plan",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'free'"
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_createdBy_user_id_fk": {
+ "name": "workspace_createdBy_user_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "user",
+ "columnsFrom": [
+ "createdBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "workspace_deletedBy_user_id_fk": {
+ "name": "workspace_deletedBy_user_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "user",
+ "columnsFrom": [
+ "deletedBy"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_publicId_unique": {
+ "name": "workspace_publicId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "publicId"
+ ]
+ },
+ "workspace_slug_unique": {
+ "name": "workspace_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ }
+ },
+ "enums": {
+ "public.board_visibility": {
+ "name": "board_visibility",
+ "schema": "public",
+ "values": [
+ "private",
+ "public"
+ ]
+ },
+ "public.card_activity_type": {
+ "name": "card_activity_type",
+ "schema": "public",
+ "values": [
+ "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"
+ ]
+ },
+ "public.source": {
+ "name": "source",
+ "schema": "public",
+ "values": [
+ "trello"
+ ]
+ },
+ "public.status": {
+ "name": "status",
+ "schema": "public",
+ "values": [
+ "started",
+ "success",
+ "failed"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "member",
+ "guest"
+ ]
+ },
+ "public.member_status": {
+ "name": "member_status",
+ "schema": "public",
+ "values": [
+ "invited",
+ "active",
+ "removed"
+ ]
+ },
+ "public.slug_type": {
+ "name": "slug_type",
+ "schema": "public",
+ "values": [
+ "reserved",
+ "premium"
+ ]
+ },
+ "public.workspace_plan": {
+ "name": "workspace_plan",
+ "schema": "public",
+ "values": [
+ "free",
+ "pro",
+ "enterprise"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json
index 9314c308..6712b3e5 100644
--- a/packages/db/migrations/meta/_journal.json
+++ b/packages/db/migrations/meta/_journal.json
@@ -8,6 +8,13 @@
"when": 1746693478656,
"tag": "20250508083758_SetupTables",
"breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1747903068807,
+ "tag": "20250522083748_AddEmailToWorkspaceMembers",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/packages/db/package.json b/packages/db/package.json
index cff2a5a4..5093601f 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -42,6 +42,7 @@
"drizzle-orm": "^0.42.0",
"drizzle-zod": "^0.5.1",
"pg": "^8.11.3",
+ "uuid": "^11.1.0",
"zod": "catalog:"
},
"devDependencies": {
diff --git a/packages/db/src/repository/member.repo.ts b/packages/db/src/repository/member.repo.ts
index c013f831..48913a92 100644
--- a/packages/db/src/repository/member.repo.ts
+++ b/packages/db/src/repository/member.repo.ts
@@ -8,7 +8,8 @@ import { generateUID } from "@kan/shared/utils";
export const create = async (
db: dbClient,
memberInput: {
- userId: string;
+ userId: string | null;
+ email: string;
workspaceId: number;
createdBy: string;
role: MemberRole;
@@ -19,6 +20,7 @@ export const create = async (
.insert(workspaceMembers)
.values({
publicId: generateUID(),
+ email: memberInput.email,
userId: memberInput.userId,
workspaceId: memberInput.workspaceId,
createdBy: memberInput.createdBy,
@@ -39,11 +41,14 @@ export const getByPublicId = async (db: dbClient, publicId: string) => {
});
};
-export const acceptInvite = async (db: dbClient, id: number) => {
+export const acceptInvite = async (
+ db: dbClient,
+ args: { memberId: number; userId: string },
+) => {
const [result] = await db
.update(workspaceMembers)
- .set({ status: "active" })
- .where(eq(workspaceMembers.id, id))
+ .set({ status: "active", userId: args.userId })
+ .where(eq(workspaceMembers.id, args.memberId))
.returning({
id: workspaceMembers.id,
publicId: workspaceMembers.publicId,
diff --git a/packages/db/src/repository/user.repo.ts b/packages/db/src/repository/user.repo.ts
index fcc86211..1c2d8f1f 100644
--- a/packages/db/src/repository/user.repo.ts
+++ b/packages/db/src/repository/user.repo.ts
@@ -1,4 +1,5 @@
import { eq } from "drizzle-orm";
+import { v4 as uuidv4 } from "uuid";
import type { dbClient } from "@kan/db/client";
import { users } from "@kan/db/schema";
@@ -29,12 +30,12 @@ export const getByEmail = (db: dbClient, email: string) => {
export const create = async (
db: dbClient,
- user: { id: string; email: string; stripeCustomerId: string },
+ user: { id?: string; email: string; stripeCustomerId?: string },
) => {
const [result] = await db
.insert(users)
.values({
- id: user.id,
+ id: user.id ?? uuidv4(),
email: user.email,
stripeCustomerId: user.stripeCustomerId,
emailVerified: false,
diff --git a/packages/db/src/repository/workspace.repo.ts b/packages/db/src/repository/workspace.repo.ts
index ce2fc830..d409a93a 100644
--- a/packages/db/src/repository/workspace.repo.ts
+++ b/packages/db/src/repository/workspace.repo.ts
@@ -11,6 +11,7 @@ export const create = async (
name: string;
slug: string;
createdBy: string;
+ createdByEmail: string;
},
) => {
const [workspace] = await db
@@ -34,6 +35,7 @@ export const create = async (
await db.insert(workspaceMembers).values({
publicId: generateUID(),
userId: workspaceInput.createdBy,
+ email: workspaceInput.createdByEmail,
workspaceId: workspace.id,
createdBy: workspaceInput.createdBy,
role: "admin",
@@ -103,6 +105,7 @@ export const getByPublicIdWithMembers = (
members: {
columns: {
publicId: true,
+ email: true,
role: true,
status: true,
},
@@ -171,6 +174,7 @@ export const getAllByUserId = (db: dbClient, userId: string) => {
},
where: and(
eq(workspaceMembers.userId, userId),
+ eq(workspaceMembers.status, "active"),
isNull(workspaceMembers.deletedAt),
),
});
diff --git a/packages/db/src/schema/workspaces.ts b/packages/db/src/schema/workspaces.ts
index faed5922..3dec09ce 100644
--- a/packages/db/src/schema/workspaces.ts
+++ b/packages/db/src/schema/workspaces.ts
@@ -54,9 +54,8 @@ export const workspaceRelations = relations(workspaces, ({ one, many }) => ({
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),
+ email: varchar("email", { length: 255 }).notNull(),
+ userId: uuid("userId").references(() => users.id),
workspaceId: bigint("workspaceId", { mode: "number" })
.notNull()
.references(() => workspaces.id, { onDelete: "cascade" }),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d1258f7b..cd801c8d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -327,6 +327,9 @@ importers:
pg:
specifier: ^8.11.3
version: 8.13.1
+ uuid:
+ specifier: ^11.1.0
+ version: 11.1.0
zod:
specifier: 'catalog:'
version: 3.24.0
@@ -5740,6 +5743,10 @@ packages:
util@0.12.5:
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
+
uuid@8.0.0:
resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==}
hasBin: true
@@ -12106,6 +12113,8 @@ snapshots:
is-typed-array: 1.1.13
which-typed-array: 1.1.16
+ uuid@11.1.0: {}
+
uuid@8.0.0: {}
uuid@9.0.1: {}
|