diff --git a/.github/workflows/tests-and-checks.yml b/.github/workflows/tests-and-checks.yml index 890e8977..0ecefbf8 100644 --- a/.github/workflows/tests-and-checks.yml +++ b/.github/workflows/tests-and-checks.yml @@ -13,6 +13,9 @@ jobs: version: 9 - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Generate Prisma Client + working-directory: apps/server + run: pnpm prisma generate - name: Build run: pnpm build - name: Generate Prisma Types diff --git a/README.md b/README.md index 36cd148d..797e94d9 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,6 @@ ```sh pnpm install -docker-compose up -``` - -```sh -# in another tab cd apps/server cp .env.example .env pnpm prisma migrate dev @@ -19,7 +14,7 @@ pnpm prisma migrate dev ### Development ```sh -docker-compose up +pnpm build --watch # in another tab cd apps/events pnpm dev @@ -28,12 +23,15 @@ cd apps/server pnpm dev ``` +Any time you make changes to the schema, you will need to run the following commands: + +```sh +cd apps/server +pnpm prisma migrate dev # this will also generate the Prisma client +``` + ## Upgrading Dependencies ```sh -pnpm up --interactive -cd apps/events -pnpm up --interactive -cd packaes/graph-framework -pnpm up --interactive +pnpm up --interactive --latest -r ``` diff --git a/apps/server/.env.example b/apps/server/.env.example index 60ffcd98..d91f9588 100644 --- a/apps/server/.env.example +++ b/apps/server/.env.example @@ -1 +1 @@ -DATABASE_URL=postgresql://prisma:prisma@0.0.0.0:5432 +DATABASE_URL="file:./dev.db" diff --git a/apps/server/.gitignore b/apps/server/.gitignore index 11ddd8db..d84e2c60 100644 --- a/apps/server/.gitignore +++ b/apps/server/.gitignore @@ -1,3 +1,6 @@ node_modules # Keep environment variables out of version control .env +# Local database files +dev.db +dev.db-journal diff --git a/apps/server/prisma/migrations/20240919063907_init/migration.sql b/apps/server/prisma/migrations/20240919063907_init/migration.sql deleted file mode 100644 index 68f983b6..00000000 --- a/apps/server/prisma/migrations/20240919063907_init/migration.sql +++ /dev/null @@ -1,154 +0,0 @@ --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL, - "username" TEXT NOT NULL, - "registrationRecord" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "UserLocker" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "clock" INTEGER NOT NULL, - "ciphertext" TEXT NOT NULL, - "nonce" TEXT NOT NULL, - "commitment" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "UserLocker_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Session" ( - "token" TEXT NOT NULL, - "sessionKey" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Session_pkey" PRIMARY KEY ("token") -); - --- CreateTable -CREATE TABLE "LoginAttempt" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "serverLoginState" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "LoginAttempt_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "UsersOnDocuments" ( - "userId" TEXT NOT NULL, - "documentId" TEXT NOT NULL, - "isAdmin" BOOLEAN NOT NULL, - - CONSTRAINT "UsersOnDocuments_pkey" PRIMARY KEY ("userId","documentId") -); - --- CreateTable -CREATE TABLE "DocumentInvitation" ( - "id" TEXT NOT NULL, - "documentId" TEXT NOT NULL, - "token" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "ciphertext" TEXT NOT NULL, - - CONSTRAINT "DocumentInvitation_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Document" ( - "id" TEXT NOT NULL, - "nameCiphertext" TEXT NOT NULL, - "nameNonce" TEXT NOT NULL, - "nameCommitment" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - "activeSnapshotId" TEXT, - - CONSTRAINT "Document_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Snapshot" ( - "id" TEXT NOT NULL, - "latestVersion" INTEGER NOT NULL, - "data" TEXT NOT NULL, - "ciphertextHash" TEXT NOT NULL, - "documentId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "clocks" JSONB NOT NULL, - "parentSnapshotUpdateClocks" JSONB NOT NULL, - "parentSnapshotProof" TEXT NOT NULL, - - CONSTRAINT "Snapshot_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Update" ( - "id" TEXT NOT NULL, - "version" INTEGER NOT NULL, - "data" TEXT NOT NULL, - "snapshotId" TEXT NOT NULL, - "clock" INTEGER NOT NULL, - "pubKey" TEXT NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); - --- CreateIndex -CREATE UNIQUE INDEX "UserLocker_userId_clock_key" ON "UserLocker"("userId", "clock"); - --- CreateIndex -CREATE UNIQUE INDEX "LoginAttempt_userId_key" ON "LoginAttempt"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "DocumentInvitation_token_key" ON "DocumentInvitation"("token"); - --- CreateIndex -CREATE UNIQUE INDEX "Document_activeSnapshotId_key" ON "Document"("activeSnapshotId"); - --- CreateIndex -CREATE UNIQUE INDEX "Update_id_key" ON "Update"("id"); - --- CreateIndex -CREATE INDEX "Update_id_version_idx" ON "Update"("id", "version"); - --- CreateIndex -CREATE UNIQUE INDEX "Update_snapshotId_version_key" ON "Update"("snapshotId", "version"); - --- CreateIndex -CREATE UNIQUE INDEX "Update_snapshotId_pubKey_clock_key" ON "Update"("snapshotId", "pubKey", "clock"); - --- AddForeignKey -ALTER TABLE "UserLocker" ADD CONSTRAINT "UserLocker_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LoginAttempt" ADD CONSTRAINT "LoginAttempt_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "UsersOnDocuments" ADD CONSTRAINT "UsersOnDocuments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "UsersOnDocuments" ADD CONSTRAINT "UsersOnDocuments_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "DocumentInvitation" ADD CONSTRAINT "DocumentInvitation_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Document" ADD CONSTRAINT "Document_activeSnapshotId_fkey" FOREIGN KEY ("activeSnapshotId") REFERENCES "Snapshot"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Snapshot" ADD CONSTRAINT "Snapshot_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Update" ADD CONSTRAINT "Update_snapshotId_fkey" FOREIGN KEY ("snapshotId") REFERENCES "Snapshot"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/server/prisma/migrations/20241108124551_init/migration.sql b/apps/server/prisma/migrations/20241108124551_init/migration.sql new file mode 100644 index 00000000..99423c92 --- /dev/null +++ b/apps/server/prisma/migrations/20241108124551_init/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "SpaceEvent" ( + "id" TEXT NOT NULL PRIMARY KEY, + "event" TEXT NOT NULL, + "spaceId" TEXT NOT NULL, + CONSTRAINT "SpaceEvent_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Space" ( + "id" TEXT NOT NULL PRIMARY KEY +); diff --git a/apps/server/prisma/migrations/migration_lock.toml b/apps/server/prisma/migrations/migration_lock.toml index fbffa92c..e5e5c470 100644 --- a/apps/server/prisma/migrations/migration_lock.toml +++ b/apps/server/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file +provider = "sqlite" \ No newline at end of file diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma index 4098bab2..dd20ede2 100644 --- a/apps/server/prisma/schema.prisma +++ b/apps/server/prisma/schema.prisma @@ -6,108 +6,18 @@ generator client { } datasource db { - provider = "postgresql" + provider = "sqlite" url = env("DATABASE_URL") } -model User { - id String @id @default(uuid()) - username String @unique - registrationRecord String - sessions Session[] - loginAttempt LoginAttempt? - createdAt DateTime @default(now()) - documents UsersOnDocuments[] - userLocker UserLocker[] +model SpaceEvent { + id String @id + event String + space Space @relation(fields: [spaceId], references: [id]) + spaceId String } -model UserLocker { - id String @id @default(uuid()) - userId String - user User @relation(fields: [userId], references: [id]) - clock Int - ciphertext String - nonce String - commitment String - createdAt DateTime @default(now()) - - @@unique([userId, clock]) -} - -model Session { - token String @id - sessionKey String - userId String - user User @relation(fields: [userId], references: [id]) - createdAt DateTime @default(now()) -} - -model LoginAttempt { - id String @id @default(uuid()) - userId String @unique - user User @relation(fields: [userId], references: [id]) - serverLoginState String - createdAt DateTime @default(now()) -} - -model UsersOnDocuments { - user User @relation(fields: [userId], references: [id]) - userId String - document Document @relation(fields: [documentId], references: [id]) - documentId String - isAdmin Boolean - - @@id([userId, documentId]) -} - -model DocumentInvitation { - id String @id @default(uuid()) - document Document @relation(fields: [documentId], references: [id]) - documentId String - token String @unique - createdAt DateTime @default(now()) - ciphertext String -} - -model Document { - id String @id - nameCiphertext String - nameNonce String - nameCommitment String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - users UsersOnDocuments[] - documentInvitations DocumentInvitation[] - activeSnapshot Snapshot? @relation(name: "activeSnapshot", fields: [activeSnapshotId], references: [id]) - activeSnapshotId String? @unique - snapshots Snapshot[] -} - -model Snapshot { - id String @id - latestVersion Int - data String - ciphertextHash String - document Document @relation(fields: [documentId], references: [id]) - documentId String - updates Update[] - activeSnapshotDocument Document? @relation("activeSnapshot") - createdAt DateTime @default(now()) - clocks Json - parentSnapshotUpdateClocks Json - parentSnapshotProof String -} - -model Update { - id String @unique // composed out of snapshotId, pubKey, clock - version Int - data String - snapshot Snapshot @relation(fields: [snapshotId], references: [id]) - snapshotId String - clock Int - pubKey String - - @@unique([snapshotId, version]) - @@unique([snapshotId, pubKey, clock]) // matches the id - @@index([id, version]) +model Space { + id String @id + events SpaceEvent[] } diff --git a/apps/server/src/handlers/createSpace.ts b/apps/server/src/handlers/createSpace.ts new file mode 100644 index 00000000..346ad490 --- /dev/null +++ b/apps/server/src/handlers/createSpace.ts @@ -0,0 +1,16 @@ +import type { CreateSpaceEvent } from 'graph-framework-space-events'; +import { prisma } from '../prisma.js'; + +export const createSpace = async (event: CreateSpaceEvent) => { + return await prisma.spaceEvent.create({ + data: { + event: JSON.stringify(event), + id: '1', + space: { + create: { + id: event.transaction.id, + }, + }, + }, + }); +}; diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index cc2a7bec..1466d37a 100755 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -2,9 +2,11 @@ import cors from 'cors'; import 'dotenv/config'; import { Schema } from 'effect'; import express from 'express'; +import type { CreateSpaceEvent } from 'graph-framework-space-events'; import { SpaceEvent } from 'graph-framework-space-events'; import type WebSocket from 'ws'; import { WebSocketServer } from 'ws'; +import { createSpace } from './handlers/createSpace.js'; const webSocketServer = new WebSocketServer({ noServer: true }); const PORT = process.env.PORT !== undefined ? Number.parseInt(process.env.PORT) : 3030; @@ -31,7 +33,9 @@ webSocketServer.on('connection', async (webSocket: WebSocket) => { const result = decodeEvent(rawData); if (result._tag === 'Right') { const data = result.right; - console.log('Message received', data); + if (data.transaction.type === 'create-space') { + await createSpace(data as CreateSpaceEvent); + } } }); webSocket.on('close', () => { diff --git a/apps/server/src/prisma.ts b/apps/server/src/prisma.ts new file mode 100644 index 00000000..9b6c4ce3 --- /dev/null +++ b/apps/server/src/prisma.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from '@prisma/client'; + +export const prisma = new PrismaClient(); diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 1d0969d5..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: "3" -services: - postgres: - image: postgres:latest - ports: - - "5432:5432" - environment: - POSTGRES_USER: prisma - POSTGRES_PASSWORD: prisma - volumes: - - postgres:/var/lib/postgresql/data - # Make sure log colors show up correctly - tty: true -volumes: - postgres: \ No newline at end of file diff --git a/docs/technology-stack.md b/docs/technology-stack.md index dccdf3f4..fa571258 100644 --- a/docs/technology-stack.md +++ b/docs/technology-stack.md @@ -8,7 +8,7 @@ - Testing - Vitest - Backend - - Database: Postgres + Prisma (explore Neo4j as well) + - Database: Sqlite + Prisma (explore Neo4j as well) - API: Websocket - Effect - Other diff --git a/packages/graph-framework-space-events/src/types.ts b/packages/graph-framework-space-events/src/types.ts index bbcde1be..178a536e 100644 --- a/packages/graph-framework-space-events/src/types.ts +++ b/packages/graph-framework-space-events/src/types.ts @@ -25,44 +25,52 @@ export const SpaceState = Schema.Struct({ export type SpaceState = Schema.Schema.Type; -export const SpaceEvent = Schema.Union( - Schema.Struct({ - transaction: Schema.Struct({ - type: Schema.Literal('create-space'), - id: Schema.String, - creatorSignaturePublicKey: Schema.String, - creatorEncryptionPublicKey: Schema.String, - }), - author: Schema.Struct({ - publicKey: Schema.String, - signature: Schema.String, - }), +export const CreateSpaceEvent = Schema.Struct({ + transaction: Schema.Struct({ + type: Schema.Literal('create-space'), + id: Schema.String, + creatorSignaturePublicKey: Schema.String, + creatorEncryptionPublicKey: Schema.String, }), - Schema.Struct({ - transaction: Schema.Struct({ - type: Schema.Literal('delete-space'), - id: Schema.String, - }), - author: Schema.Struct({ - publicKey: Schema.String, - signature: Schema.String, - }), + author: Schema.Struct({ + publicKey: Schema.String, + signature: Schema.String, }), - Schema.Struct({ - transaction: Schema.Struct({ - type: Schema.Literal('create-invitation'), - id: Schema.String, - ciphertext: Schema.String, - nonce: Schema.String, - signaturePublicKey: Schema.String, - encryptionPublicKey: Schema.String, - }), - author: Schema.Struct({ - publicKey: Schema.String, - signature: Schema.String, - }), +}); + +export type CreateSpaceEvent = Schema.Schema.Type; + +export const DeleteSpaceEvent = Schema.Struct({ + transaction: Schema.Struct({ + type: Schema.Literal('delete-space'), + id: Schema.String, + }), + author: Schema.Struct({ + publicKey: Schema.String, + signature: Schema.String, + }), +}); + +export type DeleteSpaceEvent = Schema.Schema.Type; + +export const CreateInvitationEvent = Schema.Struct({ + transaction: Schema.Struct({ + type: Schema.Literal('create-invitation'), + id: Schema.String, + ciphertext: Schema.String, + nonce: Schema.String, + signaturePublicKey: Schema.String, + encryptionPublicKey: Schema.String, + }), + author: Schema.Struct({ + publicKey: Schema.String, + signature: Schema.String, }), -); +}); + +export type CreateInvitationEvent = Schema.Schema.Type; + +export const SpaceEvent = Schema.Union(CreateSpaceEvent, DeleteSpaceEvent, CreateInvitationEvent); export type SpaceEvent = Schema.Schema.Type;