Skip to content

Commit

Permalink
Migrate collab server to Turso
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianUribe6 committed Mar 11, 2024
1 parent 3fed779 commit 7f5ca7e
Show file tree
Hide file tree
Showing 6 changed files with 445 additions and 28 deletions.
15 changes: 15 additions & 0 deletions apps/collab-server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# The following is a sample a .env file, to get started create a copy and name
# it ".env".

NODE_ENV=development

# Must match NEXTAUTH_SECRET on front-end as it is used to sign and validate JWTs
AUTH_SECRET=secret

TURSO_DATABASE_URL=file:../../local.db

# Production

# NODE_ENV=production
# TURSO_AUTH_TOKEN=

2 changes: 2 additions & 0 deletions apps/collab-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
"@hocuspocus/extension-logger": "^2.8.1",
"@hocuspocus/extension-sqlite": "^2.8.1",
"@hocuspocus/server": "^2.8.1",
"@libsql/client": "^0.5.3",
"jose": "^5.2.2",
"mysql2": "^3.6.5",
"zod": "^3.22.4"
},
"devDependencies": {
"@jotter/typescript-config": "*",
"@types/node": "^20.10.1",
"dotenv": "^16.4.5",
"tsx": "^4.6.1",
"typescript": "^5.3.2"
}
Expand Down
16 changes: 12 additions & 4 deletions apps/collab-server/src/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from "zod";
import "dotenv/config";

type EnvironmentVariables = z.infer<typeof envSchema>;

Expand All @@ -12,11 +13,18 @@ declare global {
const envSchema = z.object({
NODE_ENV: z.union([z.literal("development"), z.literal("production")]),
PORT: z.optional(z.string()),
DATABASE_USER: z.string().min(1),
DATABASE_PASS: z.string().min(1),
DATABASE_HOST: z.string().min(1),
DATABASE_NAME: z.string().min(1),
AUTH_SECRET: z.string().min(1),
TURSO_DATABASE_URL: z.string().min(1),
TURSO_AUTH_TOKEN: z
.string()
.min(1)
.optional()
.refine((token) => {
if (process.env.NODE_ENV === "production") {
return Boolean(token);
}
return true;
}, "A token is required to access production database."),
});

export const env = envSchema.parse(process.env);
23 changes: 4 additions & 19 deletions apps/collab-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Database } from "@hocuspocus/extension-database";
import { Logger } from "@hocuspocus/extension-logger";
import { Hocuspocus } from "@hocuspocus/server";
import MySQL from "./extensions/MySQL.js";
import { env } from "./env.js";
import { jwtVerify } from "jose";
import { env } from "./env.js";
import tursoConfiguration from "./turso-config.js";

const PORT = env.PORT ? Number(env.PORT) : 1234;
const IS_PRODUCTION = env.NODE_ENV === "production";

// Configure the server …
const server = new Hocuspocus({
Expand All @@ -16,22 +16,7 @@ const server = new Hocuspocus({
await jwtVerify(data.token, key);
},

extensions: [
new Logger(),

new MySQL({
user: env.DATABASE_USER,
host: env.DATABASE_HOST,
database: env.DATABASE_NAME,
password: env.DATABASE_PASS,

// Enable SSL on production. Production database will reject the connection
// when this flag is not set.
ssl: {
rejectUnauthorized: IS_PRODUCTION,
},
}),
],
extensions: [new Logger(), new Database(tursoConfiguration)],
});

// // … and run it!
Expand Down
41 changes: 41 additions & 0 deletions apps/collab-server/src/turso-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { DatabaseConfiguration } from "@hocuspocus/extension-database";
import { createClient } from "@libsql/client";
import { env } from "./env.js";

const client = createClient({
url: env.TURSO_DATABASE_URL,
authToken: env.TURSO_AUTH_TOKEN,
});

const tursoConfiguration: DatabaseConfiguration = {
async fetch({ documentName }) {
const res = await client.execute({
sql: `SELECT name, data
FROM document
WHERE name = ?
`,
args: [documentName],
});

const data = res.rows[0]?.data;
if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}

return null;
},

async store({ documentName, state: documentData }) {
const now = new Date().getTime();
await client.execute({
sql: `UPDATE document
SET data = ?,
modifiedOn = ?
WHERE name = ?
`,
args: [documentName, now, documentData],
});
},
};

export default tursoConfiguration;
Loading

0 comments on commit 7f5ca7e

Please sign in to comment.