Skip to content

Commit

Permalink
Add authentication, and Database integration (#73)
Browse files Browse the repository at this point in the history
* Add next auth and drizzleORM dependencies

* Configure Next auth and refactor env variables configuration

* Add login screen

* Get user directly from auth

* Add a User profile menu

* Run database and collaboration server on dev script

* Configure prisma adapter and update collab server schema to match

* Add volume to persist development database changes

* Add development database  URL to public .env file

* Add notebooks schema

* Let front-end handle Document table creation to prevent inconsistencies

* Store author of each document

* Create/ delete documents  from database directly.

Move /:workspaceId/:noteId to /notes

Group all authentication routes under (auth) directory

Refactor useSelf() and useOthers() to use user directly from auth

Remove workspaceId parameter on createItem

* Create get document by name action

* Make Editor a separate component, and also separate the configuration to its own domain

* Store all document types in the same database table.
Add "notebookName" parameter to StartCollaboration.
Remove useWorkspace.
Initialize Sidebar items "id" as empty string

* Migrate to drizzle-orm

* Remove documentNotebookTable, add notebookId path again

* Authorize collaboration server connection using JWTs

* Move useSelf and useOthers from userStore to collaboration lib

* Pass token value to collaborationPlugin and StartCollaboration

* Configure Discord, Google login

* remove unnecessary migrations

* Add example environment variables

* Use nanoid for notebookId

* Patch  Next.js ESLint warnings

* Add missing dependencies

* Refactor sign in screen

* Generate initial migrations

* Drop foreign key constraints

* Add notebook to document maping drizzle relations
  • Loading branch information
BrianUribe6 authored Feb 29, 2024
1 parent 2ba7038 commit 0621541
Show file tree
Hide file tree
Showing 65 changed files with 10,912 additions and 7,007 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
# The environment variables defined here should only be used during development
DATABASE_PASS=pass

DATABASE_NAME=jotter
DATABASE_NAME=jotter

AUTH_SECRET=secret
1 change: 1 addition & 0 deletions apps/collab-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@hocuspocus/extension-logger": "^2.8.1",
"@hocuspocus/extension-sqlite": "^2.8.1",
"@hocuspocus/server": "^2.8.1",
"jose": "^5.2.2",
"mysql2": "^3.6.5",
"zod": "^3.22.4"
},
Expand Down
9 changes: 5 additions & 4 deletions apps/collab-server/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ 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(),
DATABASE_PASS: z.string(),
DATABASE_HOST: z.string(),
DATABASE_NAME: 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),
});

export const env = envSchema.parse(process.env);
39 changes: 8 additions & 31 deletions apps/collab-server/src/extensions/MySQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,14 @@ import { Database } from "@hocuspocus/extension-database";
import { Extension, fetchPayload, storePayload } from "@hocuspocus/server";
import mysql, { ConnectionOptions } from "mysql2/promise";

const CREATE_TABLE_SCHEMA = `CREATE TABLE IF NOT EXISTS documents (
name varchar(255) NOT NULL,
data blob NOT NULL,
created_on timestamp DEFAULT CURRENT_TIMESTAMP(),
modified_on timestamp,
PRIMARY KEY (name)
)`;

const UPSERT_DOCUMENT_QUERY = `INSERT INTO documents (name, data, modified_on)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
data = ?,
modified_on = ?
const UPDATE_DOCUMENT_QUERY = `UPDATE document
SET data = ?,
modifiedOn = CURRENT_TIMESTAMP()
WHERE name = ?
`;

const SELECT_DOCUMENT_BY_NAME_QUERY = `SELECT name, data
FROM documents
FROM document
WHERE name = ?
`;

Expand All @@ -37,15 +28,6 @@ class MySQL extends Database implements Extension {
return mysql.createConnection(this.connectionConfig);
}

async onConfigure() {
const conn = await this.connect();
try {
await conn.execute(CREATE_TABLE_SCHEMA);
} finally {
conn.end();
}
}

async fetch({ documentName }: fetchPayload): Promise<Uint8Array | null> {
const conn = await this.connect();
try {
Expand All @@ -62,15 +44,10 @@ class MySQL extends Database implements Extension {

async store({ documentName, state: documentData }: storePayload) {
const conn = await this.connect();
const now = new Date();
try {
await conn.execute(UPSERT_DOCUMENT_QUERY, [
documentName,
documentData,
now,
documentData,
now,
]);
await conn.execute(UPDATE_DOCUMENT_QUERY, [documentData, documentName]);
} catch (error) {
console.error(error);
} finally {
conn.end();
}
Expand Down
7 changes: 7 additions & 0 deletions apps/collab-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ 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";

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

// Configure the server …
const server = new Hocuspocus({
port: PORT,

async onAuthenticate(data) {
const key = new TextEncoder().encode(env.AUTH_SECRET);
await jwtVerify(data.token, key);
},

extensions: [
new Logger(),

Expand Down
9 changes: 8 additions & 1 deletion apps/front-end/.env
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
NEXT_PUBLIC_COLLAB_SERVER_URL=ws://localhost:1234
NEXT_PUBLIC_COLLAB_SERVER_URL=ws://localhost:1234

# Local development database
# This should match the same one configured for the collaboration server
DATABASE_URL=mysql://root:pass@localhost/jotter


NEXTAUTH_URL=http://localhost:3000
11 changes: 11 additions & 0 deletions apps/front-end/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Next Auth
NEXTAUTH_SECRET=

GITHUB_CLIENT_ID=
GITHUB_SECRET=

GOOGLE_CLIENT_ID=
GOOGLE_SECRET=

DISCORD_CLIENT_ID=
DISCORD_SECRET=
10 changes: 10 additions & 0 deletions apps/front-end/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";

export default defineConfig({
schema: "./src/schema.ts",
driver: "mysql2",
out: "./drizzle",
dbCredentials: {
uri: process.env.DATABASE_URL!,
},
});
57 changes: 57 additions & 0 deletions apps/front-end/drizzle/0000_panoramic_james_howlett.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
CREATE TABLE `account` (
`userId` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL,
`provider` varchar(255) NOT NULL,
`providerAccountId` varchar(255) NOT NULL,
`refresh_token` varchar(255),
`access_token` varchar(255),
`expires_at` int,
`token_type` varchar(255),
`scope` varchar(255),
`id_token` varchar(2048),
`session_state` varchar(255),
CONSTRAINT `account_provider_providerAccountId_pk` PRIMARY KEY(`provider`,`providerAccountId`)
);
--> statement-breakpoint
CREATE TABLE `document` (
`name` char(21) NOT NULL,
`createdOn` timestamp DEFAULT (now()),
`modifiedOn` timestamp DEFAULT (now()),
`data` blob,
CONSTRAINT `document_name` PRIMARY KEY(`name`)
);
--> statement-breakpoint
CREATE TABLE `notebook` (
`id` char(21) NOT NULL,
`documentName` char(21) NOT NULL,
`authorId` varchar(255) NOT NULL,
CONSTRAINT `notebook_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `session` (
`sessionToken` varchar(255) NOT NULL,
`userId` varchar(255) NOT NULL,
`expires` timestamp NOT NULL,
CONSTRAINT `session_sessionToken` PRIMARY KEY(`sessionToken`)
);
--> statement-breakpoint
CREATE TABLE `user` (
`id` varchar(255) NOT NULL,
`name` varchar(255),
`email` varchar(255),
`emailVerified` timestamp(3) DEFAULT (now()),
`image` varchar(255),
CONSTRAINT `user_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `verificationToken` (
`identifier` varchar(255) NOT NULL,
`token` varchar(255) NOT NULL,
`expires` timestamp NOT NULL,
CONSTRAINT `verificationToken_identifier_token_pk` PRIMARY KEY(`identifier`,`token`)
);
--> statement-breakpoint
ALTER TABLE `account` ADD CONSTRAINT `account_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `notebook` ADD CONSTRAINT `notebook_documentName_document_name_fk` FOREIGN KEY (`documentName`) REFERENCES `document`(`name`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `notebook` ADD CONSTRAINT `notebook_authorId_user_id_fk` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session` ADD CONSTRAINT `session_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;
7 changes: 7 additions & 0 deletions apps/front-end/drizzle/0001_rare_doorman.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TABLE `account` DROP FOREIGN KEY `account_userId_user_id_fk`;
--> statement-breakpoint
ALTER TABLE `notebook` DROP FOREIGN KEY `notebook_documentName_document_name_fk`;
--> statement-breakpoint
ALTER TABLE `notebook` DROP FOREIGN KEY `notebook_authorId_user_id_fk`;
--> statement-breakpoint
ALTER TABLE `session` DROP FOREIGN KEY `session_userId_user_id_fk`;
8 changes: 8 additions & 0 deletions apps/front-end/drizzle/0002_ambitious_ser_duncan.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE `notebook_document` (
`notebookId` char(21) NOT NULL,
`documentName` char(21) NOT NULL,
CONSTRAINT `notebook_document_notebookId` PRIMARY KEY(`notebookId`)
);
--> statement-breakpoint
ALTER TABLE `document` ADD `id` char(21) NOT NULL;--> statement-breakpoint
ALTER TABLE `notebook` DROP COLUMN `documentName`;
Loading

0 comments on commit 0621541

Please sign in to comment.