Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/(dashboard)/[tenant]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ export default async function ConsoleLayout(props: {
redirect("/start");
}

const ready = await isDatabaseReady();
// Parallelize database ready check and notifications wire setup
const [ready, notificationsWire] = await Promise.all([
isDatabaseReady(),
caller.user.getNotificationsWire(),
]);

if (!ready) {
redirect("/start");
}

const notificationsWire = await caller.user.getNotificationsWire();

return (
<TRPCReactProvider>
<NuqsAdapter>
Expand Down
66 changes: 29 additions & 37 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 0 additions & 6 deletions instrumentation-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@ import * as Sentry from "@sentry/nextjs";

Sentry.init({
dsn: process.env.SENTRY_DSN,

// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,
// Enable logs to be sent to Sentry
enableLogs: true,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});

export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
89 changes: 55 additions & 34 deletions lib/utils/useDatabase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import path from "node:path";
import { currentUser } from "@clerk/nextjs/server";
import { sql } from "drizzle-orm";
import { upstashCache } from "drizzle-orm/cache/upstash";
import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { err, ok, type Result, ResultAsync } from "neverthrow";
Expand All @@ -8,7 +10,9 @@ import { addUserToOpsDb } from "@/ops/useOps";
import * as schema from "../../drizzle/schema";
import { getOwner } from "./useOwner";
import { addUserToTenantDb } from "./useUser";
import { upstashCache } from "drizzle-orm/cache/upstash";

const connectionPool = new Map<string, Database>();
const connectionTimestamps = new Map<string, number>();

function handleError(message: string) {
return (error: unknown) => {
Expand All @@ -25,44 +29,49 @@ function getDatabaseName(ownerId: string): Result<string, string> {
}

export async function isDatabaseReady(): Promise<boolean> {
return await ResultAsync.fromPromise(
migrateDatabase(),
handleError("Migration failed"),
)
.andThen(() =>
ResultAsync.fromPromise(
addUserToTenantDb(),
handleError("Failed to add user to tenant database"),
),
)
.andThen(() =>
ResultAsync.fromPromise(
addUserToOpsDb(),
handleError("Failed to add user to ops database"),
),
)
.match(
() => true,
() => false,
);
try {
const migrationResult = await migrateDatabase();

if (!migrationResult) {
return false;
}

const userData = await currentUser();
if (!userData) {
throw new Error("No user found");
}

await Promise.all([addUserToTenantDb(userData), addUserToOpsDb(userData)]);

return true;
} catch (error) {
console.error("Database setup failed:", error);
return false;
}
}

async function migrateDatabase(): Promise<boolean> {
return await ResultAsync.fromPromise(
const dbResult = await ResultAsync.fromPromise(
database(),
handleError("Failed to get database"),
)
.andThen((db) => {
const migrationsFolder = path.resolve(process.cwd(), "drizzle");
return ResultAsync.fromPromise(
migrate(db, { migrationsFolder: migrationsFolder }),
handleError("Failed to migrate database"),
);
})
.match(
() => true,
() => false,
);
);

if (dbResult.isErr()) {
return false;
}

const db = dbResult.value;
const migrationsFolder = path.resolve(process.cwd(), "drizzle");

const migrateResult = await ResultAsync.fromPromise(
migrate(db, { migrationsFolder: migrationsFolder }),
handleError("Failed to migrate database"),
);

return migrateResult.match(
() => true,
() => false,
);
}

export async function database(): Promise<Database> {
Expand All @@ -75,6 +84,12 @@ export async function database(): Promise<Database> {
}

export async function getDatabaseForOwner(ownerId: string): Promise<Database> {
const cachedConnection = connectionPool.get(ownerId);
if (cachedConnection) {
connectionTimestamps.set(ownerId, Date.now());
return cachedConnection;
}

const databaseName = getDatabaseName(ownerId).match(
(value) => {
return value;
Expand Down Expand Up @@ -109,6 +124,9 @@ export async function getDatabaseForOwner(ownerId: string): Promise<Database> {
},
);

connectionPool.set(ownerId, tenantDb);
connectionTimestamps.set(ownerId, Date.now());

return tenantDb;
}

Expand All @@ -122,6 +140,9 @@ export async function deleteDatabase(ownerId: string) {
},
);

connectionPool.delete(ownerId);
connectionTimestamps.delete(ownerId);

const sslMode = process.env.DATABASE_SSL === "true" ? "?sslmode=require" : "";

const ownerDb = drizzle(`${process.env.DATABASE_URL}/manage${sslMode}`, {
Expand Down
11 changes: 7 additions & 4 deletions lib/utils/useUser.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { currentUser } from "@clerk/nextjs/server";
import { user } from "@/drizzle/schema";
import type { User } from "@/drizzle/types";
import { currentUser } from "@clerk/nextjs/server";
import { database } from "./useDatabase";
import { getOwner } from "./useOwner";

export async function addUserToTenantDb() {
const userData = await currentUser();
export async function addUserToTenantDb(
userData?: Awaited<ReturnType<typeof currentUser>>,
) {
if (!userData) {
throw new Error("No user found");
}
Expand All @@ -15,7 +16,9 @@ export async function addUserToTenantDb() {
}

const db = await database();
db.insert(user)

await db
.insert(user)
.values({
id: userData.id,
email: userData.emailAddresses?.[0].emailAddress,
Expand Down
12 changes: 8 additions & 4 deletions ops/useOps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "node:path";
import { auth, clerkClient, currentUser } from "@clerk/nextjs/server";
import { auth, clerkClient, type currentUser } from "@clerk/nextjs/server";
import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import * as schema from "./drizzle/schema";
Expand All @@ -18,9 +18,11 @@ export async function getOpsDatabase(): Promise<OpsDatabase> {
return ownerDb;
}

export async function addUserToOpsDb() {
export async function addUserToOpsDb(
userData?: Awaited<ReturnType<typeof currentUser>>,
) {
const { orgId } = await auth();
const userData = await currentUser();

if (!userData) {
throw new Error("No user found");
}
Expand All @@ -29,7 +31,9 @@ export async function addUserToOpsDb() {
}

const db = await getOpsDatabase();
db.insert(schema.opsUser)

await db
.insert(schema.opsUser)
.values({
id: userData.id,
email: userData.emailAddresses?.[0].emailAddress,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"@blocknote/core": "^0.27.2",
"@blocknote/mantine": "^0.27.2",
"@blocknote/react": "^0.27.2",
"@clerk/nextjs": "^6.20.1",
"@clerk/themes": "^2.2.47",
"@clerk/nextjs": "^6.31.6",
"@clerk/themes": "^2.4.15",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
Expand Down
7 changes: 0 additions & 7 deletions sentry.edge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ import * as Sentry from "@sentry/nextjs";

Sentry.init({
dsn: process.env.SENTRY_DSN,

// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,

// Enable logs to be sent to Sentry
enableLogs: true,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
7 changes: 0 additions & 7 deletions sentry.server.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ import * as Sentry from "@sentry/nextjs";

Sentry.init({
dsn: process.env.SENTRY_DSN,

// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,

// Enable logs to be sent to Sentry
enableLogs: true,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});