feat: Add tenant table/logic #50
5 changed files with 92 additions and 3 deletions
|
|
@ -41,6 +41,15 @@ export default ts.config(
|
||||||
caughtErrorsIgnorePattern: '^_',
|
caughtErrorsIgnorePattern: '^_',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector:
|
||||||
|
'CallExpression:matches([callee.object.object.name="prisma"], [callee.object.object.name="prismaTransactionClient"], [callee.object.object.name="transactionClient"]):matches([callee.property.name="findFirst"], [callee.property.name="findMany"], [callee.property.name="updateMany"], [callee.property.name="deleteMany"], [callee.property.name="count"], [callee.property.name="aggregate"], [callee.property.name="groupBy"]):not(:has(ObjectExpression > Property[key.name="where"] > ObjectExpression > Property[key.name="tenantId"]))',
|
||||||
|
message:
|
||||||
|
'Please filter on the current tenant when using findFirst, findMany, updateMany, deleteMany, count, aggregate or groupBy.',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The primary key for the `User` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- A unique constraint covering the columns `[clerkId,tenantId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Added the required column `tenantId` to the `User` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "User_clerkId_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" DROP CONSTRAINT "User_pkey",
|
||||||
|
ADD COLUMN "tenantId" TEXT NOT NULL,
|
||||||
|
ADD CONSTRAINT "User_pkey" PRIMARY KEY ("id", "tenantId");
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Tenant" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"slug" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"clerkOrganizationId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Tenant_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Tenant_slug_key" ON "Tenant"("slug");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Tenant_clerkOrganizationId_key" ON "Tenant"("clerkOrganizationId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_clerkId_tenantId_key" ON "User"("clerkId", "tenantId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "User" ADD CONSTRAINT "User_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
@ -15,12 +15,27 @@ datasource db {
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @default(uuid())
|
||||||
clerkId String @unique
|
clerkId String
|
||||||
|
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||||
|
tenantId String
|
||||||
|
|
|||||||
|
|
||||||
email String?
|
email String?
|
||||||
name String
|
name String
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
|
|
||||||
|
@@id([id, tenantId])
|
||||||
|
@@unique([clerkId, tenantId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Tenant {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
slug String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
clerkOrganizationId String @unique
|
||||||
|
users User[]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export async function validateSession({ locals }: ServerLoadEvent) {
|
||||||
return redirect(307, '/login');
|
return redirect(307, '/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!locals.auth.orgId && locals.auth.sessionId) {
|
if ((!locals.auth.orgId && locals.auth.sessionId) || !locals.auth.orgId) {
|
||||||
// Sign out the user if they are not associated with an organization
|
// Sign out the user if they are not associated with an organization
|
||||||
await clerkSessionClient.sessions.revokeSession(locals.auth.sessionId);
|
await clerkSessionClient.sessions.revokeSession(locals.auth.sessionId);
|
||||||
return redirect(307, '/login');
|
return redirect(307, '/login');
|
||||||
|
|
@ -22,9 +22,32 @@ export async function validateSession({ locals }: ServerLoadEvent) {
|
||||||
|
|
||||||
const clerkUser = await clerkClient.users.getUser(locals.auth.userId);
|
const clerkUser = await clerkClient.users.getUser(locals.auth.userId);
|
||||||
|
|
||||||
|
const tenantClerkId = locals.auth.orgId;
|
||||||
|
|
||||||
|
let tenant = await prisma.tenant.findUnique({
|
||||||
|
where: {
|
||||||
|
clerkOrganizationId: tenantClerkId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
const organization = await clerkClient.organizations.getOrganization({
|
||||||
|
organizationId: tenantClerkId,
|
||||||
|
});
|
||||||
|
|
||||||
|
tenant = await prisma.tenant.create({
|
||||||
|
data: {
|
||||||
|
clerkOrganizationId: tenantClerkId,
|
||||||
|
name: organization.name,
|
||||||
|
slug: organization.slug ?? `tenant-${tenantClerkId}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let user = await prisma.user.findFirst({
|
let user = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
clerkId: clerkUser.id,
|
clerkId: clerkUser.id,
|
||||||
|
tenantId: tenant.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -41,6 +64,7 @@ export async function validateSession({ locals }: ServerLoadEvent) {
|
||||||
clerkId: clerkUser.id,
|
clerkId: clerkUser.id,
|
||||||
email: clerkUser.emailAddresses[0].emailAddress,
|
email: clerkUser.emailAddresses[0].emailAddress,
|
||||||
name: clerkUser.fullName ?? '',
|
name: clerkUser.fullName ?? '',
|
||||||
|
tenantId: tenant.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ builder.queryFields((t) => ({
|
||||||
users: t.prismaField({
|
users: t.prismaField({
|
||||||
type: [User],
|
type: [User],
|
||||||
resolve: async () => {
|
resolve: async () => {
|
||||||
|
// TODO: Fix this when we add a tenant context
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
return await prisma.user.findMany();
|
return await prisma.user.findMany();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue
clerkUserId*