diff --git a/.dockerignore b/.dockerignore index 94e8bea9..7a568404 100644 --- a/.dockerignore +++ b/.dockerignore @@ -23,10 +23,10 @@ !scripts/* # Server application. -!apps/server-new/package.json -!apps/server-new/tsup.config.ts -!apps/server-new/src -!apps/server-new/prisma +!apps/server/package.json +!apps/server/tsup.config.ts +!apps/server/src +!apps/server/prisma # Workspace package dependencies. !packages/hypergraph diff --git a/.gitignore b/.gitignore index 21d45685..80bd4648 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ publish output # generated prisma client apps/server/prisma/generated/ -apps/server-new/prisma/generated/ # Logs logs diff --git a/Dockerfile b/Dockerfile index d7302688..858a2195 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,15 +27,15 @@ FROM base AS build ENV DATABASE_URL="file:/data/production.sqlite" RUN \ # TODO: This initalizes the database. But we should probably remove this later. - # pnpm --filter server-new prisma migrate reset --force && \ + # pnpm --filter server prisma migrate reset --force && \ # Build the monorepo packages pnpm build && \ # Generate the prisma client - pnpm --filter server-new prisma generate && \ + pnpm --filter server prisma generate && \ # Build the server. - pnpm --filter server-new build && \ + pnpm --filter server build && \ # Create an isolated deployment for the server. - pnpm --filter server-new deploy --prod deployment --legacy && \ + pnpm --filter server deploy --prod deployment --legacy && \ # Move the runtime build artifacts into a separate directory. mkdir -p deployment/out && mv deployment/dist deployment/node_modules deployment/package.json deployment/out && \ # Add prisma client in dist diff --git a/apps/server-new/.env.example b/apps/server-new/.env.example deleted file mode 100644 index ecd0561f..00000000 --- a/apps/server-new/.env.example +++ /dev/null @@ -1,16 +0,0 @@ -# Server Configuration -PORT=3030 - -# Database -DATABASE_URL=file:./dev.db - -# Privy Configuration -PRIVY_APP_ID=your_privy_app_id -PRIVY_APP_SECRET=your_privy_app_secret - -# Hypergraph Configuration -HYPERGRAPH_CHAIN=geo-testnet -HYPERGRAPH_RPC_URL= - -# Honeycomb Configuration -HONEYCOMB_API_KEY= diff --git a/apps/server-new/CHANGELOG.md b/apps/server-new/CHANGELOG.md deleted file mode 100644 index 80d5361d..00000000 --- a/apps/server-new/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# server-new - -## 0.1.1 -### Patch Changes - -- Updated dependencies [604724b] - - @graphprotocol/hypergraph@0.6.4 diff --git a/apps/server-new/package.json b/apps/server-new/package.json deleted file mode 100644 index d9c9049a..00000000 --- a/apps/server-new/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "server-new", - "version": "0.1.1", - "private": true, - "type": "module", - "scripts": { - "dev": "tsx watch ./src/index.ts", - "build": "tsup", - "prisma": "prisma", - "prebuild": "prisma generate" - }, - "dependencies": { - "@effect/opentelemetry": "^0.56.4", - "@effect/platform": "^0.90.0", - "@effect/platform-node": "^0.96.0", - "@graphprotocol/hypergraph": "workspace:*", - "@noble/hashes": "^1.8.0", - "@prisma/client": "^6.14.0", - "@privy-io/server-auth": "^1.32.0", - "cors": "^2.8.5", - "effect": "^3.17.9", - "prisma": "^6.14.0" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/node": "^24.1.0", - "tsup": "^8.4.0", - "tsx": "^4.20.5", - "typescript": "^5.8.3", - "vitest": "^3.2.4" - } -} diff --git a/apps/server-new/prisma/migrations/20241111122738_init/migration.sql b/apps/server-new/prisma/migrations/20241111122738_init/migration.sql deleted file mode 100644 index 3eb1ca6a..00000000 --- a/apps/server-new/prisma/migrations/20241111122738_init/migration.sql +++ /dev/null @@ -1,31 +0,0 @@ --- 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 -); - --- CreateTable -CREATE TABLE "Account" ( - "id" TEXT NOT NULL PRIMARY KEY -); - --- CreateTable -CREATE TABLE "_AccountToSpace" ( - "A" TEXT NOT NULL, - "B" TEXT NOT NULL, - CONSTRAINT "_AccountToSpace_A_fkey" FOREIGN KEY ("A") REFERENCES "Account" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "_AccountToSpace_B_fkey" FOREIGN KEY ("B") REFERENCES "Space" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "_AccountToSpace_AB_unique" ON "_AccountToSpace"("A", "B"); - --- CreateIndex -CREATE INDEX "_AccountToSpace_B_index" ON "_AccountToSpace"("B"); diff --git a/apps/server-new/prisma/migrations/20241113083927_introduce_counter_and_state_to_space_events/migration.sql b/apps/server-new/prisma/migrations/20241113083927_introduce_counter_and_state_to_space_events/migration.sql deleted file mode 100644 index 40c7bd15..00000000 --- a/apps/server-new/prisma/migrations/20241113083927_introduce_counter_and_state_to_space_events/migration.sql +++ /dev/null @@ -1,25 +0,0 @@ -/* - Warnings: - - - Added the required column `counter` to the `SpaceEvent` table without a default value. This is not possible if the table is not empty. - - Added the required column `state` to the `SpaceEvent` table without a default value. This is not possible if the table is not empty. - -*/ --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_SpaceEvent" ( - "id" TEXT NOT NULL PRIMARY KEY, - "event" TEXT NOT NULL, - "state" TEXT NOT NULL, - "counter" INTEGER NOT NULL, - "spaceId" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "SpaceEvent_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_SpaceEvent" ("event", "id", "spaceId") SELECT "event", "id", "spaceId" FROM "SpaceEvent"; -DROP TABLE "SpaceEvent"; -ALTER TABLE "new_SpaceEvent" RENAME TO "SpaceEvent"; -CREATE UNIQUE INDEX "SpaceEvent_spaceId_counter_key" ON "SpaceEvent"("spaceId", "counter"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/apps/server-new/prisma/migrations/20241114170708_add_invitation/migration.sql b/apps/server-new/prisma/migrations/20241114170708_add_invitation/migration.sql deleted file mode 100644 index 9c67be40..00000000 --- a/apps/server-new/prisma/migrations/20241114170708_add_invitation/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ --- CreateTable -CREATE TABLE "Invitation" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceId" TEXT NOT NULL, - "accountAddress" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "Invitation_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Invitation_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); diff --git a/apps/server-new/prisma/migrations/20241116161206_extend_invitation/migration.sql b/apps/server-new/prisma/migrations/20241116161206_extend_invitation/migration.sql deleted file mode 100644 index c7ad34dc..00000000 --- a/apps/server-new/prisma/migrations/20241116161206_extend_invitation/migration.sql +++ /dev/null @@ -1,24 +0,0 @@ -/* - Warnings: - - - Added the required column `inviteeAccountAddress` to the `Invitation` table without a default value. This is not possible if the table is not empty. - -*/ --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Invitation" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceId" TEXT NOT NULL, - "accountAddress" TEXT NOT NULL, - "inviteeAccountAddress" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "Invitation_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Invitation_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Invitation" ("accountAddress", "createdAt", "id", "spaceId") SELECT "accountAddress", "createdAt", "id", "spaceId" FROM "Invitation"; -DROP TABLE "Invitation"; -ALTER TABLE "new_Invitation" RENAME TO "Invitation"; -CREATE UNIQUE INDEX "Invitation_spaceId_inviteeAccountAddress_key" ON "Invitation"("spaceId", "inviteeAccountAddress"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/apps/server-new/prisma/migrations/20241117202441_add_key/migration.sql b/apps/server-new/prisma/migrations/20241117202441_add_key/migration.sql deleted file mode 100644 index 40ce36bf..00000000 --- a/apps/server-new/prisma/migrations/20241117202441_add_key/migration.sql +++ /dev/null @@ -1,20 +0,0 @@ --- CreateTable -CREATE TABLE "SpaceKey" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceId" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "SpaceKey_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "SpaceKeyBox" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceKeyId" TEXT NOT NULL, - "accountAddress" TEXT NOT NULL, - "ciphertext" TEXT NOT NULL, - "nonce" TEXT NOT NULL, - "authorPublicKey" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "SpaceKeyBox_spaceKeyId_fkey" FOREIGN KEY ("spaceKeyId") REFERENCES "SpaceKey" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "SpaceKeyBox_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); diff --git a/apps/server-new/prisma/migrations/20241122132737_add_update/migration.sql b/apps/server-new/prisma/migrations/20241122132737_add_update/migration.sql deleted file mode 100644 index b7d98cf3..00000000 --- a/apps/server-new/prisma/migrations/20241122132737_add_update/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ --- CreateTable -CREATE TABLE "Update" ( - "spaceId" TEXT NOT NULL, - "clock" INTEGER NOT NULL, - "content" BLOB NOT NULL, - - PRIMARY KEY ("spaceId", "clock"), - CONSTRAINT "Update_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); diff --git a/apps/server-new/prisma/migrations/20241128184625_add_identity/migration.sql b/apps/server-new/prisma/migrations/20241128184625_add_identity/migration.sql deleted file mode 100644 index 0b475fc2..00000000 --- a/apps/server-new/prisma/migrations/20241128184625_add_identity/migration.sql +++ /dev/null @@ -1,13 +0,0 @@ --- CreateTable -CREATE TABLE "Identity" ( - "accountAddress" TEXT NOT NULL, - "ciphertext" TEXT NOT NULL, - "nonce" TEXT NOT NULL, - "signaturePublicKey" TEXT NOT NULL, - "encryptionPublicKey" TEXT NOT NULL, - "accountProof" TEXT NOT NULL, - "keyProof" TEXT NOT NULL, - - PRIMARY KEY ("accountAddress", "nonce"), - CONSTRAINT "Identity_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); diff --git a/apps/server-new/prisma/migrations/20241212164639_add_session_nonce_token/migration.sql b/apps/server-new/prisma/migrations/20241212164639_add_session_nonce_token/migration.sql deleted file mode 100644 index 2a501af8..00000000 --- a/apps/server-new/prisma/migrations/20241212164639_add_session_nonce_token/migration.sql +++ /dev/null @@ -1,4 +0,0 @@ --- AlterTable -ALTER TABLE "Account" ADD COLUMN "sessionNonce" TEXT; -ALTER TABLE "Account" ADD COLUMN "sessionToken" TEXT; -ALTER TABLE "Account" ADD COLUMN "sessionTokenExpires" DATETIME; diff --git a/apps/server-new/prisma/migrations/20241212184317_add_account_token_index/migration.sql b/apps/server-new/prisma/migrations/20241212184317_add_account_token_index/migration.sql deleted file mode 100644 index a72cc7b4..00000000 --- a/apps/server-new/prisma/migrations/20241212184317_add_account_token_index/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- CreateIndex -CREATE INDEX "Account_sessionToken_idx" ON "Account"("sessionToken"); diff --git a/apps/server-new/prisma/migrations/20250129220359_add_update_signature/migration.sql b/apps/server-new/prisma/migrations/20250129220359_add_update_signature/migration.sql deleted file mode 100644 index b4b7a68e..00000000 --- a/apps/server-new/prisma/migrations/20250129220359_add_update_signature/migration.sql +++ /dev/null @@ -1,30 +0,0 @@ -/* - Warnings: - - - Added the required column `accountAddress` to the `Update` table without a default value. This is not possible if the table is not empty. - - Added the required column `updateId` to the `Update` table without a default value. This is not possible if the table is not empty. - - Added the required column `signatureHex` to the `Update` table without a default value. This is not possible if the table is not empty. - - Added the required column `signatureRecovery` to the `Update` table without a default value. This is not possible if the table is not empty. - -*/ --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Update" ( - "spaceId" TEXT NOT NULL, - "clock" INTEGER NOT NULL, - "content" BLOB NOT NULL, - "accountAddress" TEXT NOT NULL, - "signatureHex" TEXT NOT NULL, - "signatureRecovery" INTEGER NOT NULL, - "updateId" TEXT NOT NULL, - - PRIMARY KEY ("spaceId", "clock"), - CONSTRAINT "Update_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Update_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Update" ("clock", "content", "spaceId") SELECT "clock", "content", "spaceId" FROM "Update"; -DROP TABLE "Update"; -ALTER TABLE "new_Update" RENAME TO "Update"; -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/apps/server-new/prisma/migrations/20250428155829_add_inboxes/migration.sql b/apps/server-new/prisma/migrations/20250428155829_add_inboxes/migration.sql deleted file mode 100644 index 1e7419f0..00000000 --- a/apps/server-new/prisma/migrations/20250428155829_add_inboxes/migration.sql +++ /dev/null @@ -1,50 +0,0 @@ --- CreateTable -CREATE TABLE "SpaceInbox" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceId" TEXT NOT NULL, - "isPublic" BOOLEAN NOT NULL, - "authPolicy" TEXT NOT NULL, - "encryptionPublicKey" TEXT NOT NULL, - "encryptedSecretKey" TEXT NOT NULL, - "spaceEventId" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "SpaceInbox_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "SpaceInbox_spaceEventId_fkey" FOREIGN KEY ("spaceEventId") REFERENCES "SpaceEvent" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "SpaceInboxMessage" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceInboxId" TEXT NOT NULL, - "ciphertext" TEXT NOT NULL, - "signatureHex" TEXT, - "signatureRecovery" INTEGER, - "authorAccountAddress" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "SpaceInboxMessage_spaceInboxId_fkey" FOREIGN KEY ("spaceInboxId") REFERENCES "SpaceInbox" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AccountInbox" ( - "id" TEXT NOT NULL PRIMARY KEY, - "accountAddress" TEXT NOT NULL, - "isPublic" BOOLEAN NOT NULL, - "authPolicy" TEXT NOT NULL, - "encryptionPublicKey" TEXT NOT NULL, - "signatureHex" TEXT NOT NULL, - "signatureRecovery" INTEGER NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "AccountInbox_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AccountInboxMessage" ( - "id" TEXT NOT NULL PRIMARY KEY, - "accountInboxId" TEXT NOT NULL, - "ciphertext" TEXT NOT NULL, - "signatureHex" TEXT, - "signatureRecovery" INTEGER, - "authorAccountAddress" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "AccountInboxMessage_accountInboxId_fkey" FOREIGN KEY ("accountInboxId") REFERENCES "AccountInbox" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); diff --git a/apps/server-new/prisma/migrations/20250611173316_introduce_connect/migration.sql b/apps/server-new/prisma/migrations/20250611173316_introduce_connect/migration.sql deleted file mode 100644 index 35cd3579..00000000 --- a/apps/server-new/prisma/migrations/20250611173316_introduce_connect/migration.sql +++ /dev/null @@ -1,191 +0,0 @@ -/* - Warnings: - - - You are about to drop the `Identity` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `_AccountToSpace` table. If the table is not empty, all the data it contains will be lost. - - The primary key for the `Account` table will be changed. If it partially fails, the table could be left without primary key constraint. - - You are about to drop the column `id` on the `Account` table. All the data in the column will be lost. - - You are about to drop the column `sessionNonce` on the `Account` table. All the data in the column will be lost. - - You are about to drop the column `sessionToken` on the `Account` table. All the data in the column will be lost. - - You are about to drop the column `sessionTokenExpires` on the `Account` table. All the data in the column will be lost. - - Added the required column `address` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `connectAccountProof` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `connectAddress` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `connectCiphertext` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `connectEncryptionPublicKey` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `connectKeyProof` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `connectNonce` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `connectSignaturePublicKey` to the `Account` table without a default value. This is not possible if the table is not empty. - - Added the required column `infoAuthorAddress` to the `Space` table without a default value. This is not possible if the table is not empty. - - Added the required column `infoContent` to the `Space` table without a default value. This is not possible if the table is not empty. - - Added the required column `infoSignatureHex` to the `Space` table without a default value. This is not possible if the table is not empty. - - Added the required column `infoSignatureRecovery` to the `Space` table without a default value. This is not possible if the table is not empty. - - Added the required column `name` to the `Space` table without a default value. This is not possible if the table is not empty. - -*/ --- DropIndex -DROP INDEX "_AccountToSpace_B_index"; - --- DropIndex -DROP INDEX "_AccountToSpace_AB_unique"; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "Identity"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "_AccountToSpace"; -PRAGMA foreign_keys=on; - --- CreateTable -CREATE TABLE "AppIdentity" ( - "address" TEXT NOT NULL PRIMARY KEY, - "ciphertext" TEXT NOT NULL, - "nonce" TEXT NOT NULL, - "signaturePublicKey" TEXT NOT NULL, - "encryptionPublicKey" TEXT NOT NULL, - "accountProof" TEXT NOT NULL, - "keyProof" TEXT NOT NULL, - "accountAddress" TEXT NOT NULL, - "appId" TEXT NOT NULL, - "sessionToken" TEXT NOT NULL, - "sessionTokenExpires" DATETIME NOT NULL, - CONSTRAINT "AppIdentity_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("address") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "InvitationTargetApp" ( - "id" TEXT NOT NULL PRIMARY KEY, - "invitationId" TEXT NOT NULL, - CONSTRAINT "InvitationTargetApp_invitationId_fkey" FOREIGN KEY ("invitationId") REFERENCES "Invitation" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "_space-members" ( - "A" TEXT NOT NULL, - "B" TEXT NOT NULL, - CONSTRAINT "_space-members_A_fkey" FOREIGN KEY ("A") REFERENCES "Account" ("address") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "_space-members_B_fkey" FOREIGN KEY ("B") REFERENCES "Space" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "_AppIdentityToSpace" ( - "A" TEXT NOT NULL, - "B" TEXT NOT NULL, - CONSTRAINT "_AppIdentityToSpace_A_fkey" FOREIGN KEY ("A") REFERENCES "AppIdentity" ("address") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "_AppIdentityToSpace_B_fkey" FOREIGN KEY ("B") REFERENCES "Space" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Account" ( - "address" TEXT NOT NULL PRIMARY KEY, - "connectAddress" TEXT NOT NULL, - "connectCiphertext" TEXT NOT NULL, - "connectNonce" TEXT NOT NULL, - "connectSignaturePublicKey" TEXT NOT NULL, - "connectEncryptionPublicKey" TEXT NOT NULL, - "connectAccountProof" TEXT NOT NULL, - "connectKeyProof" TEXT NOT NULL -); -DROP TABLE "Account"; -ALTER TABLE "new_Account" RENAME TO "Account"; -CREATE UNIQUE INDEX "Account_connectAddress_key" ON "Account"("connectAddress"); -CREATE TABLE "new_AccountInbox" ( - "id" TEXT NOT NULL PRIMARY KEY, - "accountAddress" TEXT NOT NULL, - "isPublic" BOOLEAN NOT NULL, - "authPolicy" TEXT NOT NULL, - "encryptionPublicKey" TEXT NOT NULL, - "signatureHex" TEXT NOT NULL, - "signatureRecovery" INTEGER NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "AccountInbox_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("address") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_AccountInbox" ("accountAddress", "authPolicy", "createdAt", "encryptionPublicKey", "id", "isPublic", "signatureHex", "signatureRecovery") SELECT "accountAddress", "authPolicy", "createdAt", "encryptionPublicKey", "id", "isPublic", "signatureHex", "signatureRecovery" FROM "AccountInbox"; -DROP TABLE "AccountInbox"; -ALTER TABLE "new_AccountInbox" RENAME TO "AccountInbox"; -CREATE TABLE "new_Invitation" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceId" TEXT NOT NULL, - "accountAddress" TEXT NOT NULL, - "inviteeAccountAddress" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "Invitation_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Invitation_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("address") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Invitation" ("accountAddress", "createdAt", "id", "inviteeAccountAddress", "spaceId") SELECT "accountAddress", "createdAt", "id", "inviteeAccountAddress", "spaceId" FROM "Invitation"; -DROP TABLE "Invitation"; -ALTER TABLE "new_Invitation" RENAME TO "Invitation"; -CREATE UNIQUE INDEX "Invitation_spaceId_inviteeAccountAddress_key" ON "Invitation"("spaceId", "inviteeAccountAddress"); -CREATE TABLE "new_Space" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "infoContent" BLOB NOT NULL, - "infoAuthorAddress" TEXT NOT NULL, - "infoSignatureHex" TEXT NOT NULL, - "infoSignatureRecovery" INTEGER NOT NULL, - CONSTRAINT "Space_infoAuthorAddress_fkey" FOREIGN KEY ("infoAuthorAddress") REFERENCES "Account" ("address") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Space" ("id") SELECT "id" FROM "Space"; -DROP TABLE "Space"; -ALTER TABLE "new_Space" RENAME TO "Space"; -CREATE TABLE "new_SpaceKeyBox" ( - "id" TEXT NOT NULL PRIMARY KEY, - "spaceKeyId" TEXT NOT NULL, - "ciphertext" TEXT NOT NULL, - "nonce" TEXT NOT NULL, - "authorPublicKey" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "accountAddress" TEXT NOT NULL, - "appIdentityAddress" TEXT, - CONSTRAINT "SpaceKeyBox_spaceKeyId_fkey" FOREIGN KEY ("spaceKeyId") REFERENCES "SpaceKey" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "SpaceKeyBox_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("address") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "SpaceKeyBox_appIdentityAddress_fkey" FOREIGN KEY ("appIdentityAddress") REFERENCES "AppIdentity" ("address") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_SpaceKeyBox" ("accountAddress", "authorPublicKey", "ciphertext", "createdAt", "id", "nonce", "spaceKeyId") SELECT "accountAddress", "authorPublicKey", "ciphertext", "createdAt", "id", "nonce", "spaceKeyId" FROM "SpaceKeyBox"; -DROP TABLE "SpaceKeyBox"; -ALTER TABLE "new_SpaceKeyBox" RENAME TO "SpaceKeyBox"; -CREATE UNIQUE INDEX "SpaceKeyBox_spaceKeyId_nonce_key" ON "SpaceKeyBox"("spaceKeyId", "nonce"); -CREATE TABLE "new_Update" ( - "spaceId" TEXT NOT NULL, - "clock" INTEGER NOT NULL, - "content" BLOB NOT NULL, - "accountAddress" TEXT NOT NULL, - "signatureHex" TEXT NOT NULL, - "signatureRecovery" INTEGER NOT NULL, - "updateId" TEXT NOT NULL, - - PRIMARY KEY ("spaceId", "clock"), - CONSTRAINT "Update_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Update_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("address") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Update" ("accountAddress", "clock", "content", "signatureHex", "signatureRecovery", "spaceId", "updateId") SELECT "accountAddress", "clock", "content", "signatureHex", "signatureRecovery", "spaceId", "updateId" FROM "Update"; -DROP TABLE "Update"; -ALTER TABLE "new_Update" RENAME TO "Update"; -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; - --- CreateIndex -CREATE INDEX "AppIdentity_sessionToken_idx" ON "AppIdentity"("sessionToken"); - --- CreateIndex -CREATE UNIQUE INDEX "AppIdentity_accountAddress_appId_key" ON "AppIdentity"("accountAddress", "appId"); - --- CreateIndex -CREATE UNIQUE INDEX "AppIdentity_accountAddress_nonce_key" ON "AppIdentity"("accountAddress", "nonce"); - --- CreateIndex -CREATE UNIQUE INDEX "_space-members_AB_unique" ON "_space-members"("A", "B"); - --- CreateIndex -CREATE INDEX "_space-members_B_index" ON "_space-members"("B"); - --- CreateIndex -CREATE UNIQUE INDEX "_AppIdentityToSpace_AB_unique" ON "_AppIdentityToSpace"("A", "B"); - --- CreateIndex -CREATE INDEX "_AppIdentityToSpace_B_index" ON "_AppIdentityToSpace"("B"); diff --git a/apps/server-new/prisma/migrations/20250620005807_add_connect_signer_address/migration.sql b/apps/server-new/prisma/migrations/20250620005807_add_connect_signer_address/migration.sql deleted file mode 100644 index 61bca61a..00000000 --- a/apps/server-new/prisma/migrations/20250620005807_add_connect_signer_address/migration.sql +++ /dev/null @@ -1,26 +0,0 @@ -/* - Warnings: - - - Added the required column `connectSignerAddress` to the `Account` table without a default value. This is not possible if the table is not empty. - -*/ --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Account" ( - "address" TEXT NOT NULL PRIMARY KEY, - "connectAddress" TEXT NOT NULL, - "connectCiphertext" TEXT NOT NULL, - "connectNonce" TEXT NOT NULL, - "connectSignaturePublicKey" TEXT NOT NULL, - "connectEncryptionPublicKey" TEXT NOT NULL, - "connectAccountProof" TEXT NOT NULL, - "connectKeyProof" TEXT NOT NULL, - "connectSignerAddress" TEXT NOT NULL -); -INSERT INTO "new_Account" ("address", "connectAccountProof", "connectAddress", "connectCiphertext", "connectEncryptionPublicKey", "connectKeyProof", "connectNonce", "connectSignaturePublicKey") SELECT "address", "connectAccountProof", "connectAddress", "connectCiphertext", "connectEncryptionPublicKey", "connectKeyProof", "connectNonce", "connectSignaturePublicKey" FROM "Account"; -DROP TABLE "Account"; -ALTER TABLE "new_Account" RENAME TO "Account"; -CREATE UNIQUE INDEX "Account_connectAddress_key" ON "Account"("connectAddress"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/apps/server-new/prisma/migrations/20250627185421_remove_app_identity_nonce/migration.sql b/apps/server-new/prisma/migrations/20250627185421_remove_app_identity_nonce/migration.sql deleted file mode 100644 index b5aed7b7..00000000 --- a/apps/server-new/prisma/migrations/20250627185421_remove_app_identity_nonce/migration.sql +++ /dev/null @@ -1,29 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `nonce` on the `AppIdentity` table. All the data in the column will be lost. - -*/ --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_AppIdentity" ( - "address" TEXT NOT NULL PRIMARY KEY, - "ciphertext" TEXT NOT NULL, - "signaturePublicKey" TEXT NOT NULL, - "encryptionPublicKey" TEXT NOT NULL, - "accountProof" TEXT NOT NULL, - "keyProof" TEXT NOT NULL, - "accountAddress" TEXT NOT NULL, - "appId" TEXT NOT NULL, - "sessionToken" TEXT NOT NULL, - "sessionTokenExpires" DATETIME NOT NULL, - CONSTRAINT "AppIdentity_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "Account" ("address") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_AppIdentity" ("accountAddress", "accountProof", "address", "appId", "ciphertext", "encryptionPublicKey", "keyProof", "sessionToken", "sessionTokenExpires", "signaturePublicKey") SELECT "accountAddress", "accountProof", "address", "appId", "ciphertext", "encryptionPublicKey", "keyProof", "sessionToken", "sessionTokenExpires", "signaturePublicKey" FROM "AppIdentity"; -DROP TABLE "AppIdentity"; -ALTER TABLE "new_AppIdentity" RENAME TO "AppIdentity"; -CREATE INDEX "AppIdentity_sessionToken_idx" ON "AppIdentity"("sessionToken"); -CREATE UNIQUE INDEX "AppIdentity_accountAddress_appId_key" ON "AppIdentity"("accountAddress", "appId"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/apps/server-new/prisma/migrations/migration_lock.toml b/apps/server-new/prisma/migrations/migration_lock.toml deleted file mode 100644 index 2a5a4441..00000000 --- a/apps/server-new/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (e.g., Git) -provider = "sqlite" diff --git a/apps/server-new/prisma/schema.prisma b/apps/server-new/prisma/schema.prisma deleted file mode 100644 index d5f629e2..00000000 --- a/apps/server-new/prisma/schema.prisma +++ /dev/null @@ -1,189 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client" - output = "generated/client" - moduleFormat = "esm" - binaryTargets = ["native", "linux-musl-openssl-3.0.x", "linux-musl-arm64-openssl-3.0.x"] // linux needed for the deployment -} - -datasource db { - provider = "sqlite" - url = env("DATABASE_URL") -} - -model SpaceEvent { - id String @id - event String - state String - counter Int - space Space @relation(fields: [spaceId], references: [id]) - spaceId String - createdAt DateTime @default(now()) - inboxes SpaceInbox[] - - @@unique([spaceId, counter]) -} - -model Space { - id String @id - events SpaceEvent[] - members Account[] @relation("space-members") - invitations Invitation[] - keys SpaceKey[] - updates Update[] - inboxes SpaceInbox[] - appIdentities AppIdentity[] - name String // TODO: remove this field and use infoContent instead - infoContent Bytes - infoAuthor Account @relation(fields: [infoAuthorAddress], references: [address]) - infoAuthorAddress String - infoSignatureHex String - infoSignatureRecovery Int -} - -model SpaceKey { - id String @id - space Space @relation(fields: [spaceId], references: [id]) - spaceId String - createdAt DateTime @default(now()) - keyBoxes SpaceKeyBox[] -} - -model SpaceKeyBox { - id String @id - spaceKey SpaceKey @relation(fields: [spaceKeyId], references: [id]) - spaceKeyId String - ciphertext String - nonce String - authorPublicKey String - createdAt DateTime @default(now()) - account Account @relation(fields: [accountAddress], references: [address]) - accountAddress String - appIdentity AppIdentity? @relation(fields: [appIdentityAddress], references: [address]) - appIdentityAddress String? - - @@unique([spaceKeyId, nonce]) -} - -model SpaceInbox { - id String @id - space Space @relation(fields: [spaceId], references: [id]) - spaceId String - isPublic Boolean - authPolicy String - encryptionPublicKey String - encryptedSecretKey String - spaceEvent SpaceEvent @relation(fields: [spaceEventId], references: [id]) - spaceEventId String - messages SpaceInboxMessage[] - createdAt DateTime @default(now()) -} - -model SpaceInboxMessage { - id String @id @default(uuid(4)) - spaceInbox SpaceInbox @relation(fields: [spaceInboxId], references: [id]) - spaceInboxId String - ciphertext String - signatureHex String? - signatureRecovery Int? - authorAccountAddress String? - createdAt DateTime @default(now()) -} - -model Account { - address String @id - spaces Space[] @relation("space-members") - invitations Invitation[] - appIdentities AppIdentity[] - - updates Update[] - inboxes AccountInbox[] - connectAddress String @unique - connectCiphertext String - connectNonce String - connectSignaturePublicKey String - connectEncryptionPublicKey String - connectAccountProof String - connectKeyProof String - infoAuthor Space[] - spaceKeyBoxes SpaceKeyBox[] - connectSignerAddress String -} - -model AppIdentity { - address String @id - ciphertext String - signaturePublicKey String - encryptionPublicKey String - accountProof String - keyProof String - account Account @relation(fields: [accountAddress], references: [address]) - accountAddress String - spaces Space[] - spaceKeyBoxes SpaceKeyBox[] - appId String - sessionToken String - sessionTokenExpires DateTime - - @@unique([accountAddress, appId]) - @@index([sessionToken]) -} - -model AccountInbox { - id String @id - account Account @relation(fields: [accountAddress], references: [address]) - accountAddress String - isPublic Boolean - authPolicy String - encryptionPublicKey String - signatureHex String - signatureRecovery Int - messages AccountInboxMessage[] - createdAt DateTime @default(now()) -} - -model AccountInboxMessage { - id String @id @default(uuid(7)) - accountInbox AccountInbox @relation(fields: [accountInboxId], references: [id]) - accountInboxId String - ciphertext String - signatureHex String? - signatureRecovery Int? - authorAccountAddress String? - createdAt DateTime @default(now()) -} - -model Invitation { - id String @id - space Space @relation(fields: [spaceId], references: [id]) - spaceId String - account Account @relation(fields: [accountAddress], references: [address]) - accountAddress String - inviteeAccountAddress String - createdAt DateTime @default(now()) - targetApps InvitationTargetApp[] - - @@unique([spaceId, inviteeAccountAddress]) -} - -model InvitationTargetApp { - id String @id - invitation Invitation @relation(fields: [invitationId], references: [id]) - invitationId String -} - -model Update { - space Space @relation(fields: [spaceId], references: [id]) - spaceId String - clock Int - content Bytes - account Account @relation(fields: [accountAddress], references: [address]) - accountAddress String - signatureHex String - signatureRecovery Int - updateId String - - @@id([spaceId, clock]) -} diff --git a/apps/server-new/src/index.ts b/apps/server-new/src/index.ts deleted file mode 100644 index e52f14d4..00000000 --- a/apps/server-new/src/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as Otlp from '@effect/opentelemetry/Otlp'; -import { FetchHttpClient, PlatformConfigProvider } from '@effect/platform'; -import { NodeContext, NodeRuntime } from '@effect/platform-node'; -import { Effect, Layer, Logger, Option, Redacted } from 'effect'; -import * as Config from './config/honeycomb.ts'; -import { server } from './server.ts'; - -const Observability = Layer.unwrapEffect( - Effect.gen(function* () { - const apiKey = yield* Config.honeycombApiKeyConfig; - if (Option.isNone(apiKey)) { - return Layer.empty; - } - - return Otlp.layer({ - baseUrl: 'https://api.honeycomb.io', - headers: { - 'x-honeycomb-team': Redacted.value(apiKey.value), - }, - resource: { - serviceName: 'hypergraph-server', - }, - }).pipe(Layer.provide(FetchHttpClient.layer)); - }), -); - -const layer = server.pipe( - Layer.provide(Logger.structured), - // Layer.provide(Logger.pretty), - Layer.provide(Observability), - Layer.provide(PlatformConfigProvider.layerDotEnvAdd('.env')), - Layer.provide(NodeContext.layer), -); - -NodeRuntime.runMain(Layer.launch(layer), { - disablePrettyLogger: true, -}); diff --git a/apps/server-new/tsconfig.json b/apps/server-new/tsconfig.json deleted file mode 100644 index 1f557c70..00000000 --- a/apps/server-new/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["src", "tsup.config.ts"], - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - "composite": false, - "incremental": false, - "declaration": false, - "declarationMap": false, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "exactOptionalPropertyTypes": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - } -} diff --git a/apps/server-new/tsup.config.ts b/apps/server-new/tsup.config.ts deleted file mode 100644 index 7ed5a144..00000000 --- a/apps/server-new/tsup.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'tsup'; - -export default defineConfig(() => ({ - entry: ['src/index.ts'], - format: ['esm'], - target: 'node22', - clean: true, - sourcemap: true, - minify: false, - splitting: false, - dts: false, -})); diff --git a/apps/server/.cursor/rules/backend-api.mdc b/apps/server/.cursor/rules/backend-api.mdc deleted file mode 100644 index d40e3b17..00000000 --- a/apps/server/.cursor/rules/backend-api.mdc +++ /dev/null @@ -1,59 +0,0 @@ ---- -description: Server backend API patterns and conventions -globs: ["src/**/*.ts"] -alwaysApply: false ---- - -# Server Backend API Rules - -## Technology Stack -- Use Express.js for HTTP server -- Use Prisma for database operations -- Use Bun for runtime -- Use WebSocket for real-time communication -- Use Effect for functional programming - -## API Design -- Use RESTful API patterns -- Implement proper HTTP status codes -- Use consistent error response format -- Implement proper request validation -- Use middleware for cross-cutting concerns - -## Database -- Use Prisma for type-safe database access -- Implement proper migrations -- Use transactions for data consistency -- Implement proper indexing strategies -- Follow database naming conventions - -## Authentication -- Use Privy for authentication -- Implement proper SIWE validation -- Use secure session management -- Implement proper authorization checks - -## Security -- Use HPKE for encryption -- Implement proper CORS policies -- Use secure headers -- Validate all inputs -- Implement rate limiting - -## Error Handling -- Use Effect for error handling -- Implement proper error logging -- Return consistent error responses -- Handle edge cases gracefully - -## Performance -- Implement proper caching strategies -- Use connection pooling -- Optimize database queries -- Implement proper monitoring - -## Testing -- Test API endpoints thoroughly -- Mock external dependencies -- Test authentication flows -- Test error scenarios diff --git a/apps/server/.env.example b/apps/server/.env.example index 0c0af8cf..ecd0561f 100644 --- a/apps/server/.env.example +++ b/apps/server/.env.example @@ -1,4 +1,16 @@ -DATABASE_URL="file:./dev.db" -PRIVY_APP_SECRET="TODO" -PRIVY_APP_ID="TODO" -HYPERGRAPH_CHAIN="geo-testnet" +# Server Configuration +PORT=3030 + +# Database +DATABASE_URL=file:./dev.db + +# Privy Configuration +PRIVY_APP_ID=your_privy_app_id +PRIVY_APP_SECRET=your_privy_app_secret + +# Hypergraph Configuration +HYPERGRAPH_CHAIN=geo-testnet +HYPERGRAPH_RPC_URL= + +# Honeycomb Configuration +HONEYCOMB_API_KEY= diff --git a/apps/server/CHANGELOG.md b/apps/server/CHANGELOG.md index 31670c64..a2408c82 100644 --- a/apps/server/CHANGELOG.md +++ b/apps/server/CHANGELOG.md @@ -1,86 +1,7 @@ # server -## 0.1.12 +## 0.1.1 ### Patch Changes - Updated dependencies [604724b] - @graphprotocol/hypergraph@0.6.4 - -## 0.1.11 -### Patch Changes - -- Updated dependencies [0546a02] -- Updated dependencies [2baa671] - - @graphprotocol/hypergraph@0.6.3 - -## 0.1.10 -### Patch Changes - -- Updated dependencies - - @graphprotocol/hypergraph@0.6.2 - -## 0.1.9 -### Patch Changes - -- Updated dependencies [8ab0100] - - @graphprotocol/hypergraph@0.6.1 - -## 0.1.8 -### Patch Changes - -- Updated dependencies [cbc98ed] - - @graphprotocol/hypergraph@0.6.0 - -## 0.1.7 -### Patch Changes - -- Updated dependencies [9d22312] - - @graphprotocol/hypergraph@0.5.0 - -## 0.1.6 -### Patch Changes - -- Updated dependencies [4410012] - - @graphprotocol/hypergraph@0.4.2 - -## 0.1.5 -### Patch Changes - -- Updated dependencies [e324e68] - - @graphprotocol/hypergraph@0.4.1 - -## 0.1.4 -### Patch Changes - -- Updated dependencies [9b29006] - - @graphprotocol/hypergraph@0.4.0 - -## 0.1.3 -### Patch Changes - -- Updated dependencies [b37483e] -- Updated dependencies [f4fd295] - - @graphprotocol/hypergraph@0.3.1 - -## 0.1.2 -### Patch Changes - -- Updated dependencies [cb54727] -- Updated dependencies [c0035eb] -- Updated dependencies [f8ccaed] - - @graphprotocol/hypergraph@0.3.0 - -## 0.1.1 -### Patch Changes - -- Updated dependencies [8622688] - - @graphprotocol/hypergraph@0.2.0 - -## 0.1.0 -### Patch Changes - -- 114d743: breaking changes of the authentication flow to improve security and fix invitations -- Updated dependencies [dd4746a] -- Updated dependencies [fd0a13f] -- Updated dependencies [114d743] - - @graphprotocol/hypergraph@0.1.0 diff --git a/apps/server-new/CLAUDE.md b/apps/server/CLAUDE.md similarity index 99% rename from apps/server-new/CLAUDE.md rename to apps/server/CLAUDE.md index a9fec626..84655f95 100644 --- a/apps/server-new/CLAUDE.md +++ b/apps/server/CLAUDE.md @@ -59,7 +59,6 @@ This is non-negotiable and applies to every single file modification. - Strict mode enabled - Effect patterns preferred (Effect.fn over Effect.gen) - No emit configuration (build handled by tsup) -- Path aliases configured: `server-new/*` maps to `./src/*` ### Project Structure - `src/` - Source code directory diff --git a/apps/server/LICENSE b/apps/server/LICENSE deleted file mode 100644 index 3ddb85bf..00000000 --- a/apps/server/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024-present Geo Browser, PB LLC and other contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/apps/server/api-docs/api-summary.md b/apps/server/api-docs/api-summary.md deleted file mode 100644 index 9a6dff6e..00000000 --- a/apps/server/api-docs/api-summary.md +++ /dev/null @@ -1,81 +0,0 @@ -# API Routes Summary - -## HTTP Endpoints - -### Health Check -- **GET /** - Server status check - -### Connect API (Privy Authentication) -- **GET /connect/spaces** - List spaces for authenticated account -- **POST /connect/spaces** - Create new space -- **POST /connect/add-app-identity-to-spaces** - Add app identity to spaces -- **POST /connect/identity** - Create connect identity -- **GET /connect/identity/encrypted** - Get encrypted identity data -- **GET /connect/app-identity/:appId** - Get app identity by ID -- **POST /connect/app-identity** - Create app identity - -### Identity API -- **GET /whoami** - Get current account from session token -- **GET /connect/identity** - Get public connect identity (public) -- **GET /identity** - Get identity by public key or app ID (public) - -### Inbox API (Public) -- **GET /spaces/:spaceId/inboxes** - List public space inboxes -- **GET /spaces/:spaceId/inboxes/:inboxId** - Get space inbox details -- **POST /spaces/:spaceId/inboxes/:inboxId/messages** - Post to space inbox -- **GET /accounts/:accountAddress/inboxes** - List public account inboxes -- **GET /accounts/:accountAddress/inboxes/:inboxId** - Get account inbox details -- **POST /accounts/:accountAddress/inboxes/:inboxId/messages** - Post to account inbox - -## WebSocket Endpoints - -### Connection -- **ws://?token=[sessionToken]** - Establish WebSocket connection - -### Message Types -- **subscribe-space** - Subscribe to space updates -- **list-spaces** - List accessible spaces -- **list-invitations** - List pending invitations -- **create-space-event** - Create new space -- **create-invitation-event** - Invite to space -- **accept-invitation-event** - Accept invitation -- **create-space-inbox-event** - Create space inbox -- **create-account-inbox** - Create account inbox -- **get-latest-space-inbox-messages** - Get recent inbox messages -- **get-latest-account-inbox-messages** - Get recent account messages -- **get-account-inboxes** - List account inboxes -- **create-update** - Create CRDT update - -### Broadcast Events -- **space-event** - Space event notification -- **updates-notification** - CRDT updates -- **space-inbox-message** - New inbox message -- **account-inbox-message** - New account message -- **account-inbox** - Account inbox created - -## Authentication Methods - -1. **Privy ID Token**: Used by Connect app endpoints - - Header: `privy-id-token` - - Validates signer permissions - -2. **Session Token**: Used by app identities - - Header: `Authorization: Bearer ` - - 30-day expiry - -3. **Public Endpoints**: No authentication required - - Identity lookups - - Public inbox access - -## Common Response Formats - -### Success -- 200 OK with JSON response -- Empty object `{}` for successful writes - -### Errors -- 400 Bad Request - Invalid parameters -- 401 Unauthorized - Authentication failed -- 403 Forbidden - Insufficient permissions -- 404 Not Found - Resource not found -- 500 Internal Server Error - Server error \ No newline at end of file diff --git a/apps/server/api-docs/domain-model-overview.md b/apps/server/api-docs/domain-model-overview.md deleted file mode 100644 index cffcfff6..00000000 --- a/apps/server/api-docs/domain-model-overview.md +++ /dev/null @@ -1,212 +0,0 @@ -# Domain Model Overview - -## Core Entities - -### Account -Central user entity representing an identity in the system. -```prisma -model Account { - address String @id - spaces Space[] @relation("space-members") - invitations Invitation[] - appIdentities AppIdentity[] - updates Update[] - inboxes AccountInbox[] - connectAddress String @unique - connectCiphertext String - connectNonce String - connectSignaturePublicKey String - connectEncryptionPublicKey String - connectAccountProof String - connectKeyProof String - connectSignerAddress String - spaceKeyBoxes SpaceKeyBox[] - infoAuthor Space[] -} -``` - -### Space -Collaborative workspace containing members, events, and data. -```prisma -model Space { - id String @id - events SpaceEvent[] - members Account[] @relation("space-members") - invitations Invitation[] - keys SpaceKey[] - updates Update[] - inboxes SpaceInbox[] - appIdentities AppIdentity[] - name String - infoContent Bytes - infoAuthorAddress String - infoSignatureHex String - infoSignatureRecovery Int -} -``` - -### AppIdentity -Application-specific identity linked to an account. -```prisma -model AppIdentity { - address String @id - ciphertext String - signaturePublicKey String - encryptionPublicKey String - accountProof String - keyProof String - accountAddress String - spaces Space[] - spaceKeyBoxes SpaceKeyBox[] - appId String - sessionToken String - sessionTokenExpires DateTime - @@unique([accountAddress, appId]) -} -``` - -### SpaceEvent -Append-only log of events within a space. -```prisma -model SpaceEvent { - id String @id - event String - state String - counter Int - spaceId String - createdAt DateTime @default(now()) - inboxes SpaceInbox[] - @@unique([spaceId, counter]) -} -``` - -### SpaceKey & SpaceKeyBox -Encryption key management for spaces. -```prisma -model SpaceKey { - id String @id - spaceId String - createdAt DateTime @default(now()) - keyBoxes SpaceKeyBox[] -} - -model SpaceKeyBox { - id String @id - spaceKeyId String - ciphertext String - nonce String - authorPublicKey String - accountAddress String - appIdentityAddress String? - @@unique([spaceKeyId, nonce]) -} -``` - -### Update -CRDT updates for collaborative editing. -```prisma -model Update { - spaceId String - clock Int - content Bytes - accountAddress String - signatureHex String - signatureRecovery Int - updateId String - @@id([spaceId, clock]) -} -``` - -### Inbox System -Message queuing for spaces and accounts. - -#### SpaceInbox & SpaceInboxMessage -```prisma -model SpaceInbox { - id String @id - spaceId String - isPublic Boolean - authPolicy String - encryptionPublicKey String - encryptedSecretKey String - spaceEventId String - messages SpaceInboxMessage[] - createdAt DateTime @default(now()) -} - -model SpaceInboxMessage { - id String @id @default(uuid(4)) - spaceInboxId String - ciphertext String - signatureHex String? - signatureRecovery Int? - authorAccountAddress String? - createdAt DateTime @default(now()) -} -``` - -#### AccountInbox & AccountInboxMessage -```prisma -model AccountInbox { - id String @id - accountAddress String - isPublic Boolean - authPolicy String - encryptionPublicKey String - signatureHex String - signatureRecovery Int - messages AccountInboxMessage[] - createdAt DateTime @default(now()) -} - -model AccountInboxMessage { - id String @id @default(uuid(7)) - accountInboxId String - ciphertext String - signatureHex String? - signatureRecovery Int? - authorAccountAddress String? - createdAt DateTime @default(now()) -} -``` - -### Invitation System -```prisma -model Invitation { - id String @id - spaceId String - accountAddress String - inviteeAccountAddress String - createdAt DateTime @default(now()) - targetApps InvitationTargetApp[] - @@unique([spaceId, inviteeAccountAddress]) -} - -model InvitationTargetApp { - id String @id - invitationId String -} -``` - -## Key Relationships - -1. **Account ↔ Space**: Many-to-many through membership -2. **Account ↔ AppIdentity**: One-to-many, unique per app -3. **Space ↔ SpaceEvent**: One-to-many, append-only log -4. **Space ↔ Update**: One-to-many, CRDT updates -5. **SpaceKey ↔ SpaceKeyBox**: One-to-many, encrypted for each member -6. **Inbox ↔ Message**: One-to-many for both Space and Account inboxes - -## Authentication & Authorization - -1. **Connect Identity**: Primary identity with signature/encryption keys -2. **App Identity**: App-specific identity with session tokens -3. **Session Tokens**: 30-day expiry for app authentication -4. **Privy Integration**: External auth provider for Connect app - -## Security Features - -1. **E2E Encryption**: All sensitive data encrypted client-side -2. **Key Rotation**: New keys generated when members removed -3. **Signature Verification**: All events/messages signed -4. **Ownership Proofs**: Cryptographic proofs for identity claims \ No newline at end of file diff --git a/apps/server/api-docs/get-accounts-inbox.md b/apps/server/api-docs/get-accounts-inbox.md deleted file mode 100644 index 09b3ef31..00000000 --- a/apps/server/api-docs/get-accounts-inbox.md +++ /dev/null @@ -1,60 +0,0 @@ -# GET /accounts/:accountAddress/inboxes/:inboxId - -## Overview -Retrieves details for a specific public inbox belonging to an account. - -## HTTP Method -GET - -## Route -`/accounts/:accountAddress/inboxes/:inboxId` - -## Authentication -None required (public endpoint) - -## Request Parameters -- `accountAddress`: The account address (URL parameter) -- `inboxId`: The inbox ID (URL parameter) - -## Request Headers -None - -## Request Body -None - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseAccountInboxPublic` -```json -{ - "inbox": { - "id": "string", - "accountAddress": "string", - "isPublic": true, - "authPolicy": "string", // "requires_auth" | "anonymous" | "optional_auth" - "encryptionPublicKey": "string" - } -} -``` - -### Error Responses -- 500 Internal Server Error: Database or server error - -## Domain Model -### AccountInbox -- `id`: string (primary key) -- `accountAddress`: string (foreign key to Account) -- `isPublic`: boolean -- `authPolicy`: string -- `encryptionPublicKey`: string -- `signatureHex`: string -- `signatureRecovery`: integer - -## Implementation Details -- No authentication required (public endpoint) -- Uses `getAccountInbox` handler to fetch specific inbox -- Returns public inbox details if found -- Note: Handler should verify inbox is public before returning - -## Dependencies -- `getAccountInbox`: Fetches specific inbox from database \ No newline at end of file diff --git a/apps/server/api-docs/get-accounts-inboxes.md b/apps/server/api-docs/get-accounts-inboxes.md deleted file mode 100644 index 0b6644c0..00000000 --- a/apps/server/api-docs/get-accounts-inboxes.md +++ /dev/null @@ -1,66 +0,0 @@ -# GET /accounts/:accountAddress/inboxes - -## Overview -Lists all public inboxes for a specific account. - -## HTTP Method -GET - -## Route -`/accounts/:accountAddress/inboxes` - -## Authentication -None required (public endpoint) - -## Request Parameters -- `accountAddress`: The account address (URL parameter) - -## Request Headers -None - -## Request Body -None - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseListAccountInboxesPublic` -```json -{ - "inboxes": [ - { - "id": "string", - "accountAddress": "string", - "isPublic": true, - "authPolicy": "string", // "requires_auth" | "anonymous" | "optional_auth" - "encryptionPublicKey": "string" - } - ] -} -``` - -### Error Responses -- 500 Internal Server Error: Database or server error - -## Domain Model -### AccountInbox -- `id`: string (primary key) -- `accountAddress`: string (foreign key to Account) -- `isPublic`: boolean -- `authPolicy`: string -- `encryptionPublicKey`: string -- `signatureHex`: string -- `signatureRecovery`: integer - -### Account -- `address`: string (primary key) -- `inboxes`: AccountInbox[] relation - -## Implementation Details -- No authentication required (public endpoint) -- Uses `listPublicAccountInboxes` handler to: - - Query all inboxes for the account - - Filter to only public inboxes (isPublic = true) -- Returns list of public inbox metadata - -## Dependencies -- `listPublicAccountInboxes`: Fetches public inboxes from database \ No newline at end of file diff --git a/apps/server/api-docs/get-connect-app-identity.md b/apps/server/api-docs/get-connect-app-identity.md deleted file mode 100644 index c01c2e2f..00000000 --- a/apps/server/api-docs/get-connect-app-identity.md +++ /dev/null @@ -1,69 +0,0 @@ -# GET /connect/app-identity/:appId - -## Overview -Retrieves app identity information for a specific app ID and authenticated account. - -## HTTP Method -GET - -## Route -`/connect/app-identity/:appId` - -## Authentication -Required - Privy ID token authentication - -## Request Parameters -- `appId`: The application ID (URL parameter) - -## Request Headers -- `privy-id-token`: Privy authentication token (required) -- `account-address`: The account address (required) - -## Request Body -None - -## Response -### Success Response (200 OK) -```json -{ - "appIdentity": { - "address": "string", - "appId": "string", - "accountAddress": "string", - "sessionToken": "string", - "sessionTokenExpires": "datetime", - // Additional fields from AppIdentity model - } -} -``` - -### Error Responses -- 401 Unauthorized: Invalid authentication or insufficient permissions -- 404 Not Found: App identity not found -- 500 Internal Server Error: Missing Privy configuration - -## Domain Model -### AppIdentity -- `address`: string (primary key) -- `appId`: string -- `accountAddress`: string (foreign key to Account) -- `ciphertext`: string -- `signaturePublicKey`: string -- `encryptionPublicKey`: string -- `accountProof`: string -- `keyProof`: string -- `sessionToken`: string -- `sessionTokenExpires`: datetime - -## Implementation Details -- Validates Privy token to get signer address -- Verifies signer has permission for the specified account -- Uses `findAppIdentity` handler to search for app identity by: - - Account address - - App ID -- Returns app identity if found, 404 if not found - -## Dependencies -- `getAddressByPrivyToken`: Validates Privy token -- `isSignerForAccount`: Verifies signer permissions -- `findAppIdentity`: Searches for app identity in database \ No newline at end of file diff --git a/apps/server/api-docs/get-connect-identity-encrypted.md b/apps/server/api-docs/get-connect-identity-encrypted.md deleted file mode 100644 index d30444e4..00000000 --- a/apps/server/api-docs/get-connect-identity-encrypted.md +++ /dev/null @@ -1,63 +0,0 @@ -# GET /connect/identity/encrypted - -## Overview -Retrieves the encrypted identity data for an authenticated account. - -## HTTP Method -GET - -## Route -`/connect/identity/encrypted` - -## Authentication -Required - Privy ID token authentication - -## Request Parameters -None - -## Request Headers -- `privy-id-token`: Privy authentication token (required) -- `account-address`: The account address to retrieve identity for (required) - -## Request Body -None - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseIdentityEncrypted` -```json -{ - "keyBox": { - "accountAddress": "string", - "ciphertext": "string", - "nonce": "string", - "signer": "string" - } -} -``` - -### Error Responses -- 401 Unauthorized: Invalid authentication or insufficient permissions -- 500 Internal Server Error: Missing Privy configuration - -## Domain Model -### Account -- `address`: string (primary key) -- `connectCiphertext`: string -- `connectNonce`: string -- `connectSignerAddress`: string - -## Implementation Details -- Validates Privy token to get signer address -- Verifies signer has permission for the specified account -- Uses `getConnectIdentity` handler to fetch encrypted identity data -- Returns encrypted keyBox with: - - Account address - - Encrypted ciphertext - - Nonce for decryption - - Signer address - -## Dependencies -- `getAddressByPrivyToken`: Validates Privy token -- `isSignerForAccount`: Verifies signer permissions -- `getConnectIdentity`: Fetches identity from database \ No newline at end of file diff --git a/apps/server/api-docs/get-connect-identity.md b/apps/server/api-docs/get-connect-identity.md deleted file mode 100644 index 013c3a81..00000000 --- a/apps/server/api-docs/get-connect-identity.md +++ /dev/null @@ -1,63 +0,0 @@ -# GET /connect/identity - -## Overview -Retrieves public identity information for a given account address. - -## HTTP Method -GET - -## Route -`/connect/identity` - -## Authentication -None required (public endpoint) - -## Request Parameters -- `accountAddress`: The account address to look up (query parameter, required) - -## Request Headers -None - -## Request Body -None - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseIdentity` -```json -{ - "accountAddress": "string", - "signaturePublicKey": "string", - "encryptionPublicKey": "string", - "accountProof": "string", - "keyProof": "string" -} -``` - -### Error Responses -- 400 Bad Request: Missing accountAddress parameter -- 404 Not Found: Identity not found - Schema: `Messages.ResponseIdentityNotFoundError` - ```json - { - "accountAddress": "string" - } - ``` - -## Domain Model -### Account -- `address`: string (primary key) -- `connectSignaturePublicKey`: string -- `connectEncryptionPublicKey`: string -- `connectAccountProof`: string -- `connectKeyProof`: string - -## Implementation Details -- No authentication required (public endpoint) -- Validates required accountAddress query parameter -- Uses `getConnectIdentity` handler to fetch identity -- Returns public keys and proofs (no encrypted data) -- Returns 404 with specific error format if identity not found - -## Dependencies -- `getConnectIdentity`: Fetches identity from database \ No newline at end of file diff --git a/apps/server/api-docs/get-connect-spaces.md b/apps/server/api-docs/get-connect-spaces.md deleted file mode 100644 index 8481079c..00000000 --- a/apps/server/api-docs/get-connect-spaces.md +++ /dev/null @@ -1,103 +0,0 @@ -# GET /connect/spaces - -## Overview -Retrieves all spaces associated with an authenticated account, including space information, app identities, and key boxes. - -## HTTP Method -GET - -## Route -`/connect/spaces` - -## Authentication -Required - Privy ID token authentication - -## Request Parameters -None - -## Request Headers -- `privy-id-token`: Privy authentication token (required) -- `account-address`: The account address to retrieve spaces for (required) - -## Request Body -None - -## Response -### Success Response (200 OK) -```json -{ - "spaces": [ - { - "id": "string", - "infoContent": "hex string", - "infoAuthorAddress": "string", - "infoSignatureHex": "string", - "infoSignatureRecovery": "number", - "name": "string", - "appIdentities": [ - { - "appId": "string", - "address": "string" - } - ], - "keyBoxes": [ - { - "id": "string", - "ciphertext": "string", - "nonce": "string", - "authorPublicKey": "string" - } - ] - } - ] -} -``` - -### Error Responses -- 401 Unauthorized: Invalid or missing authentication -- 500 Internal Server Error: Missing Privy configuration - -## Domain Model -### Space -- `id`: string (primary key) -- `name`: string -- `infoContent`: bytes -- `infoAuthorAddress`: string (foreign key to Account) -- `infoSignatureHex`: string -- `infoSignatureRecovery`: integer -- `events`: SpaceEvent[] relation -- `members`: Account[] relation -- `keys`: SpaceKey[] relation -- `appIdentities`: AppIdentity[] relation - -### SpaceKey -- `id`: string (primary key) -- `spaceId`: string (foreign key to Space) -- `keyBoxes`: SpaceKeyBox[] relation - -### SpaceKeyBox -- `id`: string (primary key) -- `spaceKeyId`: string (foreign key to SpaceKey) -- `ciphertext`: string -- `nonce`: string -- `authorPublicKey`: string -- `accountAddress`: string (foreign key to Account) - -### AppIdentity -- `appId`: string -- `address`: string (primary key) -- `accountAddress`: string (foreign key to Account) - -## Implementation Details -- Uses `getAddressByPrivyToken` to validate the Privy token and get signer address -- Uses `isSignerForAccount` to verify the signer has permission for the account -- Uses `listSpacesByAccount` handler to fetch spaces -- Converts space data to response format, including: - - Converting `infoContent` from bytes to hex string - - Filtering key boxes to only include those with content - - Mapping app identities to simplified format - -## Dependencies -- `getAddressByPrivyToken`: Validates Privy token -- `isSignerForAccount`: Verifies signer permissions -- `listSpacesByAccount`: Fetches spaces from database \ No newline at end of file diff --git a/apps/server/api-docs/get-identity.md b/apps/server/api-docs/get-identity.md deleted file mode 100644 index d3254b3d..00000000 --- a/apps/server/api-docs/get-identity.md +++ /dev/null @@ -1,82 +0,0 @@ -# GET /identity - -## Overview -Retrieves public identity information for either a Connect identity or App identity based on provided parameters. - -## HTTP Method -GET - -## Route -`/identity` - -## Authentication -None required (public endpoint) - -## Request Parameters -Query parameters: -- `accountAddress`: The account address (required) -- `signaturePublicKey`: The signature public key (optional, mutually exclusive with appId) -- `appId`: The application ID (optional, mutually exclusive with signaturePublicKey) - -Note: Either `signaturePublicKey` OR `appId` must be provided, but not both. - -## Request Headers -None - -## Request Body -None - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseIdentity` -```json -{ - "accountAddress": "string", - "signaturePublicKey": "string", - "encryptionPublicKey": "string", - "accountProof": "string", - "keyProof": "string", - "appId": "string" // Optional, only present for app identities -} -``` - -### Error Responses -- 400 Bad Request: - - Missing accountAddress - - Missing both signaturePublicKey and appId -- 404 Not Found: Identity not found - Schema: `Messages.ResponseIdentityNotFoundError` - ```json - { - "accountAddress": "string" - } - ``` - -## Domain Model -### Account (Connect Identity) -- `address`: string (primary key) -- `connectSignaturePublicKey`: string -- `connectEncryptionPublicKey`: string -- `connectAccountProof`: string -- `connectKeyProof`: string - -### AppIdentity -- `address`: string (primary key) -- `appId`: string -- `accountAddress`: string (foreign key to Account) -- `signaturePublicKey`: string -- `encryptionPublicKey`: string -- `accountProof`: string -- `keyProof`: string - -## Implementation Details -- No authentication required (public endpoint) -- Validates required parameters -- Uses `getAppOrConnectIdentity` handler which: - - If signaturePublicKey provided: searches for matching identity - - If appId provided: searches for app identity with that appId -- Returns unified identity response format -- App identities include optional `appId` field in response - -## Dependencies -- `getAppOrConnectIdentity`: Flexible identity lookup handler \ No newline at end of file diff --git a/apps/server/api-docs/get-root.md b/apps/server/api-docs/get-root.md deleted file mode 100644 index c4d76368..00000000 --- a/apps/server/api-docs/get-root.md +++ /dev/null @@ -1,40 +0,0 @@ -# GET / - -## Overview -Health check endpoint that returns server status and version information. - -## HTTP Method -GET - -## Route -`/` - -## Authentication -None required - -## Request Parameters -None - -## Request Headers -None - -## Request Body -None - -## Response -### Success Response (200 OK) -``` -Server is running (v0.0.14) -``` - -## Domain Model -This endpoint does not interact with any domain models. - -## Implementation Details -- Simple health check endpoint -- Returns plain text response -- Hardcoded version string in the response -- No database queries or complex logic - -## Error Handling -This endpoint does not have specific error handling. \ No newline at end of file diff --git a/apps/server/api-docs/get-spaces-inbox.md b/apps/server/api-docs/get-spaces-inbox.md deleted file mode 100644 index 01988479..00000000 --- a/apps/server/api-docs/get-spaces-inbox.md +++ /dev/null @@ -1,60 +0,0 @@ -# GET /spaces/:spaceId/inboxes/:inboxId - -## Overview -Retrieves details for a specific public inbox within a space. - -## HTTP Method -GET - -## Route -`/spaces/:spaceId/inboxes/:inboxId` - -## Authentication -None required (public endpoint) - -## Request Parameters -- `spaceId`: The space ID (URL parameter) -- `inboxId`: The inbox ID (URL parameter) - -## Request Headers -None - -## Request Body -None - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseSpaceInboxPublic` -```json -{ - "inbox": { - "id": "string", - "spaceId": "string", - "isPublic": true, - "authPolicy": "string", // "requires_auth" | "anonymous" | "optional_auth" - "encryptionPublicKey": "string" - } -} -``` - -### Error Responses -- 500 Internal Server Error: Database or server error - -## Domain Model -### SpaceInbox -- `id`: string (primary key) -- `spaceId`: string (foreign key to Space) -- `isPublic`: boolean -- `authPolicy`: string -- `encryptionPublicKey`: string -- `encryptedSecretKey`: string -- `spaceEventId`: string (foreign key to SpaceEvent) - -## Implementation Details -- No authentication required (public endpoint) -- Uses `getSpaceInbox` handler to fetch specific inbox -- Returns public inbox details if found -- Note: Handler should verify inbox is public before returning - -## Dependencies -- `getSpaceInbox`: Fetches specific inbox from database \ No newline at end of file diff --git a/apps/server/api-docs/get-spaces-inboxes.md b/apps/server/api-docs/get-spaces-inboxes.md deleted file mode 100644 index 2f3ceec7..00000000 --- a/apps/server/api-docs/get-spaces-inboxes.md +++ /dev/null @@ -1,66 +0,0 @@ -# GET /spaces/:spaceId/inboxes - -## Overview -Lists all public inboxes for a specific space. - -## HTTP Method -GET - -## Route -`/spaces/:spaceId/inboxes` - -## Authentication -None required (public endpoint) - -## Request Parameters -- `spaceId`: The space ID (URL parameter) - -## Request Headers -None - -## Request Body -None - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseListSpaceInboxesPublic` -```json -{ - "inboxes": [ - { - "id": "string", - "spaceId": "string", - "isPublic": true, - "authPolicy": "string", // "requires_auth" | "anonymous" | "optional_auth" - "encryptionPublicKey": "string" - } - ] -} -``` - -### Error Responses -- 500 Internal Server Error: Database or server error - -## Domain Model -### SpaceInbox -- `id`: string (primary key) -- `spaceId`: string (foreign key to Space) -- `isPublic`: boolean -- `authPolicy`: string -- `encryptionPublicKey`: string -- `encryptedSecretKey`: string -- `spaceEventId`: string (foreign key to SpaceEvent) - -### Space -- `id`: string (primary key) -- `inboxes`: SpaceInbox[] relation - -## Implementation Details -- No authentication required (public endpoint) -- Uses `listPublicSpaceInboxes` handler to: - - Query all inboxes for the space - - Filter to only public inboxes (isPublic = true) -- Returns list of public inbox metadata - -## Dependencies -- `listPublicSpaceInboxes`: Fetches public inboxes from database \ No newline at end of file diff --git a/apps/server/api-docs/get-whoami.md b/apps/server/api-docs/get-whoami.md deleted file mode 100644 index 8dbdb030..00000000 --- a/apps/server/api-docs/get-whoami.md +++ /dev/null @@ -1,49 +0,0 @@ -# GET /whoami - -## Overview -Returns the account address associated with the current session token. - -## HTTP Method -GET - -## Route -`/whoami` - -## Authentication -Required - Bearer token authentication (session token) - -## Request Parameters -None - -## Request Headers -- `Authorization`: Bearer token (required) - Format: `Bearer ` - -## Request Body -None - -## Response -### Success Response (200 OK) -``` - -``` -Returns the account address as plain text. - -### Error Responses -- 401 Unauthorized: Invalid or missing session token - -## Domain Model -### AppIdentity -- `sessionToken`: string (indexed) -- `accountAddress`: string (foreign key to Account) -- `sessionTokenExpires`: datetime - -## Implementation Details -- Extracts session token from Authorization header -- Uses `getAppIdentityBySessionToken` handler to: - - Look up app identity by session token - - Verify token is not expired - - Return associated account address -- Returns account address as plain text response - -## Dependencies -- `getAppIdentityBySessionToken`: Validates session token and retrieves account \ No newline at end of file diff --git a/apps/server/api-docs/post-accounts-inbox-messages.md b/apps/server/api-docs/post-accounts-inbox-messages.md deleted file mode 100644 index 38bc692d..00000000 --- a/apps/server/api-docs/post-accounts-inbox-messages.md +++ /dev/null @@ -1,85 +0,0 @@ -# POST /accounts/:accountAddress/inboxes/:inboxId/messages - -## Overview -Posts a new message to an account inbox. Authentication requirements depend on the inbox's auth policy. - -## HTTP Method -POST - -## Route -`/accounts/:accountAddress/inboxes/:inboxId/messages` - -## Authentication -Depends on inbox auth policy: -- `requires_auth`: Signature and account address required -- `anonymous`: No authentication allowed -- `optional_auth`: Authentication optional - -## Request Parameters -- `accountAddress`: The account address (URL parameter) -- `inboxId`: The inbox ID (URL parameter) - -## Request Headers -- `Content-Type`: application/json - -## Request Body -Schema: `Messages.RequestCreateAccountInboxMessage` -```json -{ - "ciphertext": "string", - "signature": { - "hex": "string", - "recovery": "number" - }, // Optional based on auth policy - "authorAccountAddress": "string" // Optional based on auth policy -} -``` - -## Response -### Success Response (200 OK) -```json -{} -``` -Empty object on success. Message is also broadcast via WebSocket. - -### Error Responses -- 400 Bad Request: Invalid authentication for inbox policy -- 403 Forbidden: Not authorized to post to inbox -- 404 Not Found: Inbox not found -- 500 Internal Server Error: Server error - -## Domain Model -### AccountInbox -- `id`: string (primary key) -- `accountAddress`: string (foreign key to Account) -- `authPolicy`: string ("requires_auth" | "anonymous" | "optional_auth") -- `messages`: AccountInboxMessage[] relation - -### AccountInboxMessage -- `id`: string (auto-generated UUID) -- `accountInboxId`: string (foreign key to AccountInbox) -- `ciphertext`: string -- `signatureHex`: string (optional) -- `signatureRecovery`: integer (optional) -- `authorAccountAddress`: string (optional) -- `createdAt`: datetime - -## Implementation Details -- Fetches inbox to check auth policy -- Validates authentication based on policy: - - `requires_auth`: Both signature and authorAccountAddress required - - `anonymous`: Neither allowed - - `optional_auth`: Both must be provided together or neither -- If authenticated: - - Recovers public key from signature using `Inboxes.recoverAccountInboxMessageSigner` - - Verifies public key belongs to claimed account via `getAppOrConnectIdentity` -- Creates message using `createAccountInboxMessage` -- Broadcasts message to WebSocket subscribers via `broadcastAccountInboxMessage` - -## Dependencies -- `getAccountInbox`: Fetches inbox configuration -- `Inboxes.recoverAccountInboxMessageSigner`: Recovers signer from signature -- `getAppOrConnectIdentity`: Verifies identity ownership -- `createAccountInboxMessage`: Persists message -- `broadcastAccountInboxMessage`: WebSocket broadcast -- `Schema.decodeUnknownSync`: Validates request body \ No newline at end of file diff --git a/apps/server/api-docs/post-connect-add-app-identity-to-spaces.md b/apps/server/api-docs/post-connect-add-app-identity-to-spaces.md deleted file mode 100644 index 53676c9d..00000000 --- a/apps/server/api-docs/post-connect-add-app-identity-to-spaces.md +++ /dev/null @@ -1,79 +0,0 @@ -# POST /connect/add-app-identity-to-spaces - -## Overview -Adds an app identity to multiple spaces, granting the app access to those spaces. - -## HTTP Method -POST - -## Route -`/connect/add-app-identity-to-spaces` - -## Authentication -Required - Privy ID token authentication - -## Request Parameters -None - -## Request Headers -- `privy-id-token`: Privy authentication token (required) -- `Content-Type`: application/json - -## Request Body -Schema: `Messages.RequestConnectAddAppIdentityToSpaces` -```json -{ - "accountAddress": "string", - "appIdentityAddress": "string", - "spacesInput": [ - { - "spaceId": "string", - // Additional space-specific data - } - ] -} -``` - -## Response -### Success Response (200 OK) -```json -{ - "space": { - // Space object or array of spaces - } -} -``` - -### Error Responses -- 401 Unauthorized: Invalid authentication or insufficient permissions -- 500 Internal Server Error: Missing Privy configuration - -## Domain Model -### AppIdentity -- `address`: string (primary key) -- `accountAddress`: string (foreign key to Account) -- `appId`: string -- `spaces`: Space[] relation (many-to-many) - -### Space -- `id`: string (primary key) -- `appIdentities`: AppIdentity[] relation (many-to-many) - -### Account -- `address`: string (primary key) -- `appIdentities`: AppIdentity[] relation - -## Implementation Details -- Validates Privy token to get signer address -- Verifies signer has permission for the specified account -- Uses `addAppIdentityToSpaces` handler to: - - Verify the app identity belongs to the account - - Add the app identity to each specified space - - Update the many-to-many relationship -- Returns updated space information - -## Dependencies -- `getAddressByPrivyToken`: Validates Privy token -- `isSignerForAccount`: Verifies signer permissions -- `addAppIdentityToSpaces`: Updates space-app identity relationships -- `Schema.decodeUnknownSync`: Validates request body schema \ No newline at end of file diff --git a/apps/server/api-docs/post-connect-app-identity.md b/apps/server/api-docs/post-connect-app-identity.md deleted file mode 100644 index 4a499cd7..00000000 --- a/apps/server/api-docs/post-connect-app-identity.md +++ /dev/null @@ -1,88 +0,0 @@ -# POST /connect/app-identity - -## Overview -Creates a new app identity for an account with session token generation and ownership verification. - -## HTTP Method -POST - -## Route -`/connect/app-identity` - -## Authentication -Required - Privy ID token authentication - -## Request Parameters -None - -## Request Headers -- `privy-id-token`: Privy authentication token (required) -- `Content-Type`: application/json - -## Request Body -Schema: `Messages.RequestConnectCreateAppIdentity` -```json -{ - "accountAddress": "string", - "appId": "string", - "address": "string", - "ciphertext": "string", - "signaturePublicKey": "string", - "encryptionPublicKey": "string", - "accountProof": "string", - "keyProof": "string" -} -``` - -## Response -### Success Response (200 OK) -```json -{ - "appIdentity": { - "address": "string", - "appId": "string", - "accountAddress": "string", - "sessionToken": "string", - "sessionTokenExpires": "datetime", - // Additional fields - } -} -``` - -### Error Responses -- 401 Unauthorized: Invalid authentication or ownership proof -- 500 Internal Server Error: Missing Privy configuration - -## Domain Model -### AppIdentity -- `address`: string (primary key) -- `appId`: string -- `accountAddress`: string (foreign key to Account) -- `ciphertext`: string -- `signaturePublicKey`: string -- `encryptionPublicKey`: string -- `accountProof`: string -- `keyProof`: string -- `sessionToken`: string -- `sessionTokenExpires`: datetime -- Unique constraint: [accountAddress, appId] - -## Implementation Details -- Validates Privy token and verifies signer permissions -- Verifies ownership proof using `Identity.verifyIdentityOwnership` -- Generates: - - Random 32-byte session token (as hex string) - - Session expiration date (30 days from creation) -- Uses `createAppIdentity` handler to: - - Create the app identity record - - Store encrypted data and public keys - - Save session token for future authentication -- Returns created app identity with session token - -## Dependencies -- `getAddressByPrivyToken`: Validates Privy token -- `isSignerForAccount`: Verifies signer permissions -- `Identity.verifyIdentityOwnership`: Validates ownership proofs -- `createAppIdentity`: Creates app identity record -- `bytesToHex`, `randomBytes`: For session token generation -- `Schema.decodeUnknownSync`: Validates request body schema \ No newline at end of file diff --git a/apps/server/api-docs/post-connect-identity.md b/apps/server/api-docs/post-connect-identity.md deleted file mode 100644 index cca73b3f..00000000 --- a/apps/server/api-docs/post-connect-identity.md +++ /dev/null @@ -1,89 +0,0 @@ -# POST /connect/identity - -## Overview -Creates a new identity for an account with encryption and signature keys. Includes ownership verification. - -## HTTP Method -POST - -## Route -`/connect/identity` - -## Authentication -Required - Privy ID token authentication - -## Request Parameters -None - -## Request Headers -- `privy-id-token`: Privy authentication token (required) -- `Content-Type`: application/json - -## Request Body -Schema: `Messages.RequestConnectCreateIdentity` -```json -{ - "keyBox": { - "accountAddress": "string", - "signer": "string", - "ciphertext": "string", - "nonce": "string" - }, - "signaturePublicKey": "string", - "encryptionPublicKey": "string", - "accountProof": "string", - "keyProof": "string" -} -``` - -## Response -### Success Response (200 OK) -Schema: `Messages.ResponseConnectCreateIdentity` -```json -{ - "success": true -} -``` - -### Error Response (400 Bad Request) -Schema: `Messages.ResponseIdentityExistsError` -```json -{ - "accountAddress": "string" -} -``` - -### Other Error Responses -- 401 Unauthorized: Invalid authentication or ownership proof -- 500 Internal Server Error: Missing Privy configuration - -## Domain Model -### Account -- `address`: string (primary key) -- `connectAddress`: string (unique) -- `connectCiphertext`: string -- `connectNonce`: string -- `connectSignaturePublicKey`: string -- `connectEncryptionPublicKey`: string -- `connectAccountProof`: string -- `connectKeyProof`: string -- `connectSignerAddress`: string - -## Implementation Details -- Validates Privy token and ensures it matches the signer in the keyBox -- Verifies ownership proof using `Identity.verifyIdentityOwnership`: - - Validates account ownership - - Validates signature public key - - Validates proofs against the blockchain -- Uses `createIdentity` handler to: - - Create or update the Account record - - Store encrypted identity data - - Store public keys and proofs -- Returns success or specific error for existing identity - -## Dependencies -- `getAddressByPrivyToken`: Validates Privy token -- `Identity.verifyIdentityOwnership`: Validates ownership proofs -- `createIdentity`: Creates identity record -- `Schema.decodeUnknownSync`: Validates request body schema -- Chain configuration (CHAIN, RPC_URL) for blockchain verification \ No newline at end of file diff --git a/apps/server/api-docs/post-connect-spaces.md b/apps/server/api-docs/post-connect-spaces.md deleted file mode 100644 index dcfdbd57..00000000 --- a/apps/server/api-docs/post-connect-spaces.md +++ /dev/null @@ -1,102 +0,0 @@ -# POST /connect/spaces - -## Overview -Creates a new space with initial configuration, including encryption keys and space information. - -## HTTP Method -POST - -## Route -`/connect/spaces` - -## Authentication -Required - Privy ID token authentication - -## Request Parameters -None - -## Request Headers -- `privy-id-token`: Privy authentication token (required) -- `Content-Type`: application/json - -## Request Body -Schema: `Messages.RequestConnectCreateSpaceEvent` -```json -{ - "accountAddress": "string", - "event": { - // SpaceEvent object - }, - "keyBox": { - // KeyBox object - }, - "infoContent": "hex string", - "infoSignature": { - "hex": "string", - "recovery": "number" - }, - "name": "string" -} -``` - -## Response -### Success Response (200 OK) -```json -{ - "space": { - "id": "string", - // Space object - } -} -``` - -### Error Responses -- 401 Unauthorized: Invalid authentication or insufficient permissions -- 500 Internal Server Error: Missing Privy configuration - -## Domain Model -### Space -- `id`: string (primary key) -- `name`: string -- `infoContent`: bytes -- `infoAuthorAddress`: string (foreign key to Account) -- `infoSignatureHex`: string -- `infoSignatureRecovery`: integer -- `events`: SpaceEvent[] relation -- `members`: Account[] relation -- `keys`: SpaceKey[] relation - -### SpaceEvent -- `id`: string (primary key) -- `event`: string (serialized event data) -- `state`: string -- `counter`: integer -- `spaceId`: string (foreign key to Space) - -### SpaceKey -- `id`: string (primary key) -- `spaceId`: string (foreign key to Space) -- `keyBoxes`: SpaceKeyBox[] relation - -### SpaceKeyBox -- `ciphertext`: string -- `nonce`: string -- `authorPublicKey`: string -- `accountAddress`: string (foreign key to Account) - -## Implementation Details -- Validates Privy token to get signer address -- Verifies signer has permission for the specified account -- Converts hex info content to bytes -- Uses `createSpace` handler to: - - Create the space record - - Store the initial space event - - Create encryption key boxes -- Returns the created space object - -## Dependencies -- `getAddressByPrivyToken`: Validates Privy token -- `isSignerForAccount`: Verifies signer permissions -- `createSpace`: Creates space and related records -- `Schema.decodeUnknownSync`: Validates request body schema -- `Utils.hexToBytes`: Converts hex strings to byte arrays \ No newline at end of file diff --git a/apps/server/api-docs/post-spaces-inbox-messages.md b/apps/server/api-docs/post-spaces-inbox-messages.md deleted file mode 100644 index 619f77cc..00000000 --- a/apps/server/api-docs/post-spaces-inbox-messages.md +++ /dev/null @@ -1,85 +0,0 @@ -# POST /spaces/:spaceId/inboxes/:inboxId/messages - -## Overview -Posts a new message to a space inbox. Authentication requirements depend on the inbox's auth policy. - -## HTTP Method -POST - -## Route -`/spaces/:spaceId/inboxes/:inboxId/messages` - -## Authentication -Depends on inbox auth policy: -- `requires_auth`: Signature and account address required -- `anonymous`: No authentication allowed -- `optional_auth`: Authentication optional - -## Request Parameters -- `spaceId`: The space ID (URL parameter) -- `inboxId`: The inbox ID (URL parameter) - -## Request Headers -- `Content-Type`: application/json - -## Request Body -Schema: `Messages.RequestCreateSpaceInboxMessage` -```json -{ - "ciphertext": "string", - "signature": { - "hex": "string", - "recovery": "number" - }, // Optional based on auth policy - "authorAccountAddress": "string" // Optional based on auth policy -} -``` - -## Response -### Success Response (200 OK) -```json -{} -``` -Empty object on success. Message is also broadcast via WebSocket. - -### Error Responses -- 400 Bad Request: Invalid authentication for inbox policy -- 403 Forbidden: Not authorized to post to inbox -- 404 Not Found: Inbox not found -- 500 Internal Server Error: Server error - -## Domain Model -### SpaceInbox -- `id`: string (primary key) -- `spaceId`: string (foreign key to Space) -- `authPolicy`: string ("requires_auth" | "anonymous" | "optional_auth") -- `messages`: SpaceInboxMessage[] relation - -### SpaceInboxMessage -- `id`: string (auto-generated UUID) -- `spaceInboxId`: string (foreign key to SpaceInbox) -- `ciphertext`: string -- `signatureHex`: string (optional) -- `signatureRecovery`: integer (optional) -- `authorAccountAddress`: string (optional) -- `createdAt`: datetime - -## Implementation Details -- Fetches inbox to check auth policy -- Validates authentication based on policy: - - `requires_auth`: Both signature and authorAccountAddress required - - `anonymous`: Neither allowed - - `optional_auth`: Both must be provided together or neither -- If authenticated: - - Recovers public key from signature using `Inboxes.recoverSpaceInboxMessageSigner` - - Verifies public key belongs to claimed account via `getAppOrConnectIdentity` -- Creates message using `createSpaceInboxMessage` -- Broadcasts message to WebSocket subscribers via `broadcastSpaceInboxMessage` - -## Dependencies -- `getSpaceInbox`: Fetches inbox configuration -- `Inboxes.recoverSpaceInboxMessageSigner`: Recovers signer from signature -- `getAppOrConnectIdentity`: Verifies identity ownership -- `createSpaceInboxMessage`: Persists message -- `broadcastSpaceInboxMessage`: WebSocket broadcast -- `Schema.decodeUnknownSync`: Validates request body \ No newline at end of file diff --git a/apps/server/api-docs/websocket-connection.md b/apps/server/api-docs/websocket-connection.md deleted file mode 100644 index d045da1e..00000000 --- a/apps/server/api-docs/websocket-connection.md +++ /dev/null @@ -1,224 +0,0 @@ -# WebSocket Connection - -## Overview -WebSocket endpoint for real-time communication between clients and server. Handles space subscriptions, updates, events, and inbox messages. - -## Connection URL -`ws://[host]:[port]/?token=[sessionToken]` - -## Authentication -Required - Session token as query parameter - -## Connection Process -1. Client connects with session token in query parameter -2. Server validates token via `getAppIdentityBySessionToken` -3. If valid, connection established with: - - `accountAddress`: Associated account - - `appIdentityAddress`: App identity address - - `subscribedSpaces`: Empty set (populated via subscriptions) -4. If invalid, connection closed - -## WebSocket Message Types - -### Client to Server Messages -All messages use `Messages.RequestMessage` schema, serialized/deserialized via `Messages.serialize/deserialize`. - -#### subscribe-space -Subscribe to updates for a specific space. -```json -{ - "type": "subscribe-space", - "id": "spaceId" -} -``` -Response: `ResponseSpace` message with full space data - -#### list-spaces -List all spaces accessible by the app identity. -```json -{ - "type": "list-spaces" -} -``` -Response: `ResponseListSpaces` with array of spaces - -#### list-invitations -List all invitations for the account. -```json -{ - "type": "list-invitations" -} -``` -Response: `ResponseListInvitations` with invitations - -#### create-space-event -Create a new space with initial event. -```json -{ - "type": "create-space-event", - "event": { /* SpaceEvent */ }, - "keyBox": { /* KeyBox */ }, - "name": "string" -} -``` -Response: `ResponseSpace` with created space - -#### create-invitation-event -Create an invitation to a space. -```json -{ - "type": "create-invitation-event", - "spaceId": "string", - "event": { /* SpaceEvent */ }, - "keyBoxes": [ /* KeyBox[] */ ] -} -``` -Response: `ResponseSpace` and broadcasts to invitee - -#### accept-invitation-event -Accept a space invitation. -```json -{ - "type": "accept-invitation-event", - "spaceId": "string", - "event": { /* SpaceEvent */ } -} -``` -Response: `ResponseSpace` and broadcasts event - -#### create-space-inbox-event -Create an inbox for a space. -```json -{ - "type": "create-space-inbox-event", - "spaceId": "string", - "event": { /* SpaceEvent */ } -} -``` -Response: `ResponseSpace` and broadcasts event - -#### create-account-inbox -Create an inbox for the account. -```json -{ - "type": "create-account-inbox", - "accountAddress": "string", - "id": "string", - "isPublic": boolean, - "authPolicy": "string", - "encryptionPublicKey": "string", - "signature": { "hex": "string", "recovery": number } -} -``` -Broadcasts to other clients of same account - -#### get-latest-space-inbox-messages -Retrieve recent messages from a space inbox. -```json -{ - "type": "get-latest-space-inbox-messages", - "spaceId": "string", - "inboxId": "string", - "since": "datetime" // Optional -} -``` -Response: `ResponseSpaceInboxMessages` - -#### get-latest-account-inbox-messages -Retrieve recent messages from an account inbox. -```json -{ - "type": "get-latest-account-inbox-messages", - "inboxId": "string", - "since": "datetime" // Optional -} -``` -Response: `ResponseAccountInboxMessages` - -#### get-account-inboxes -List all inboxes for the account. -```json -{ - "type": "get-account-inboxes" -} -``` -Response: `ResponseAccountInboxes` - -#### create-update -Create a CRDT update for a space. -```json -{ - "type": "create-update", - "accountAddress": "string", - "spaceId": "string", - "update": "string", // Serialized update - "signature": { "hex": "string", "recovery": number }, - "updateId": "string" -} -``` -Response: `ResponseUpdateConfirmed` and broadcasts to subscribers - -### Server to Client Messages - -#### space-event (broadcast) -```json -{ - "type": "space-event", - "spaceId": "string", - "event": { /* SpaceEvent */ } -} -``` - -#### updates-notification (broadcast) -```json -{ - "type": "updates-notification", - "spaceId": "string", - "updates": { - "updates": [ /* Update[] */ ], - "firstUpdateClock": number, - "lastUpdateClock": number - } -} -``` - -#### space-inbox-message (broadcast) -```json -{ - "type": "space-inbox-message", - "spaceId": "string", - "inboxId": "string", - "message": { /* InboxMessage */ } -} -``` - -#### account-inbox-message (broadcast) -```json -{ - "type": "account-inbox-message", - "accountAddress": "string", - "inboxId": "string", - "message": { /* InboxMessage */ } -} -``` - -#### account-inbox (broadcast) -```json -{ - "type": "account-inbox", - "inbox": { /* AccountInboxPublic */ } -} -``` - -## Domain Models -See individual route documentation for detailed model descriptions. - -## Broadcasting Rules -- **Space events/updates**: Broadcast to all clients subscribed to the space -- **Account inbox messages**: Broadcast to all clients with same account address -- **Invitations**: Broadcast to invitee's connected clients - -## Error Handling -- Invalid messages are logged but don't close connection -- Authentication failures close the connection immediately -- Database errors are logged, client may not receive response \ No newline at end of file diff --git a/apps/server/package.json b/apps/server/package.json index 57b89410..c86d6105 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,38 +1,32 @@ { "name": "server", - "version": "0.1.12", + "version": "0.1.1", "private": true, "type": "module", "scripts": { - "dev": "bun run --watch ./src/index.ts", + "dev": "tsx watch ./src/index.ts", + "build": "tsup", "prisma": "prisma", - "prebuild": "prisma generate", - "build": "tsup" + "prebuild": "prisma generate" }, "dependencies": { + "@effect/opentelemetry": "^0.56.4", + "@effect/platform": "^0.90.0", + "@effect/platform-node": "^0.96.0", "@graphprotocol/hypergraph": "workspace:*", - "@hpke/chacha20poly1305": "^1.7.1", - "@hpke/core": "^1.7.4", - "@noble/ciphers": "^1.3.0", "@noble/hashes": "^1.8.0", "@prisma/client": "^6.14.0", - "@privy-io/server-auth": "^1.31.1", - "body-parser": "^2.2.0", + "@privy-io/server-auth": "^1.32.0", "cors": "^2.8.5", "effect": "^3.17.9", - "express": "^5.1.0", - "prisma": "^6.14.0", - "siwe": "^3.0.0", - "viem": "^2.34.0", - "ws": "^8.18.3" + "prisma": "^6.14.0" }, "devDependencies": { - "@types/cors": "^2.8.19", - "@types/express": "^5.0.3", - "@types/node": "^24.3.0", - "@types/pg": "^8.15.5", - "@types/ws": "^8.18.1", - "tsup": "^8.5.0", - "typescript": "^5.9.2" + "@types/cors": "^2.8.17", + "@types/node": "^24.1.0", + "tsup": "^8.4.0", + "tsx": "^4.20.5", + "typescript": "^5.8.3", + "vitest": "^3.2.4" } } diff --git a/apps/server-new/patterns/README.md b/apps/server/patterns/README.md similarity index 100% rename from apps/server-new/patterns/README.md rename to apps/server/patterns/README.md diff --git a/apps/server-new/patterns/generic-testing.md b/apps/server/patterns/generic-testing.md similarity index 100% rename from apps/server-new/patterns/generic-testing.md rename to apps/server/patterns/generic-testing.md diff --git a/apps/server-new/patterns/http-api.md b/apps/server/patterns/http-api.md similarity index 100% rename from apps/server-new/patterns/http-api.md rename to apps/server/patterns/http-api.md diff --git a/apps/server-new/patterns/layer-composition.md b/apps/server/patterns/layer-composition.md similarity index 100% rename from apps/server-new/patterns/layer-composition.md rename to apps/server/patterns/layer-composition.md diff --git a/apps/server-new/setupTests.ts b/apps/server/setupTests.ts similarity index 100% rename from apps/server-new/setupTests.ts rename to apps/server/setupTests.ts diff --git a/apps/server-new/specs/README.md b/apps/server/specs/README.md similarity index 100% rename from apps/server-new/specs/README.md rename to apps/server/specs/README.md diff --git a/apps/server-new/src/config/database.ts b/apps/server/src/config/database.ts similarity index 100% rename from apps/server-new/src/config/database.ts rename to apps/server/src/config/database.ts diff --git a/apps/server-new/src/config/honeycomb.ts b/apps/server/src/config/honeycomb.ts similarity index 100% rename from apps/server-new/src/config/honeycomb.ts rename to apps/server/src/config/honeycomb.ts diff --git a/apps/server-new/src/config/hypergraph.ts b/apps/server/src/config/hypergraph.ts similarity index 100% rename from apps/server-new/src/config/hypergraph.ts rename to apps/server/src/config/hypergraph.ts diff --git a/apps/server-new/src/config/privy.ts b/apps/server/src/config/privy.ts similarity index 100% rename from apps/server-new/src/config/privy.ts rename to apps/server/src/config/privy.ts diff --git a/apps/server-new/src/config/server.ts b/apps/server/src/config/server.ts similarity index 100% rename from apps/server-new/src/config/server.ts rename to apps/server/src/config/server.ts diff --git a/apps/server-new/src/domain/models.ts b/apps/server/src/domain/models.ts similarity index 100% rename from apps/server-new/src/domain/models.ts rename to apps/server/src/domain/models.ts diff --git a/apps/server/src/handlers/add-app-identity-to-spaces.ts b/apps/server/src/handlers/add-app-identity-to-spaces.ts deleted file mode 100644 index b528683d..00000000 --- a/apps/server/src/handlers/add-app-identity-to-spaces.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma.js'; - -type Params = { - appIdentityAddress: string; - accountAddress: string; - spacesInput: Messages.RequestConnectAddAppIdentityToSpaces['spacesInput']; -}; - -export const addAppIdentityToSpaces = async ({ appIdentityAddress, spacesInput, accountAddress }: Params) => { - return prisma.$transaction(async (prisma) => { - const appIdentity = await prisma.appIdentity.update({ - where: { - address: appIdentityAddress, - accountAddress, - }, - data: { - spaces: { - connect: spacesInput.map((space) => ({ id: space.id })), - }, - }, - }); - - const keyBoxes = spacesInput.flatMap((entry) => { - return entry.keyBoxes.map((keyBox) => { - const keyBoxId = `${keyBox.id}-${appIdentityAddress}`; - - return { - id: keyBoxId, - spaceKeyId: keyBox.id, - ciphertext: keyBox.ciphertext, - nonce: keyBox.nonce, - authorPublicKey: keyBox.authorPublicKey, - accountAddress, - appIdentityAddress, - }; - }); - }); - - await prisma.spaceKeyBox.createMany({ - data: keyBoxes, - }); - - return appIdentity; - }); -}; diff --git a/apps/server/src/handlers/applySpaceEvent.ts b/apps/server/src/handlers/applySpaceEvent.ts deleted file mode 100644 index d32627f9..00000000 --- a/apps/server/src/handlers/applySpaceEvent.ts +++ /dev/null @@ -1,127 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { Identity, SpaceEvents } from '@graphprotocol/hypergraph'; -import { Effect, Exit } from 'effect'; - -import { prisma } from '../prisma.js'; -import { getAppOrConnectIdentity } from './getAppOrConnectIdentity.js'; - -type Params = { - accountAddress: string; - spaceId: string; - event: SpaceEvents.SpaceEvent; - keyBoxes: Messages.KeyBoxWithKeyId[]; -}; - -export async function applySpaceEvent({ accountAddress, spaceId, event, keyBoxes }: Params) { - if (event.transaction.type === 'create-space') { - throw new Error('applySpaceEvent does not support create-space events.'); - } - - await prisma.$transaction(async (transaction) => { - if (event.transaction.type === 'accept-invitation') { - // verify that the account is the invitee - await transaction.invitation.findFirstOrThrow({ - where: { inviteeAccountAddress: event.author.accountAddress }, - }); - } else { - // verify that the account is a member of the space - // TODO verify that the account is a admin of the space - await transaction.space.findUniqueOrThrow({ - where: { - id: spaceId, - members: { some: { address: accountAddress } }, - }, - }); - } - - const lastEvent = await transaction.spaceEvent.findFirstOrThrow({ - where: { spaceId }, - orderBy: { counter: 'desc' }, - }); - - const getVerifiedIdentity = (accountAddressToFetch: string, publicKey: string) => { - console.log('getVerifiedIdentity', accountAddressToFetch, accountAddress); - // applySpaceEvent is only allowed to be called by the account that is applying the event - if (accountAddressToFetch !== accountAddress) { - return Effect.fail(new Identity.InvalidIdentityError()); - } - - return Effect.gen(function* () { - const identity = yield* Effect.tryPromise({ - try: () => - getAppOrConnectIdentity({ accountAddress: accountAddressToFetch, signaturePublicKey: publicKey, spaceId }), - catch: () => new Identity.InvalidIdentityError(), - }); - return identity; - }); - }; - - const result = await Effect.runPromiseExit( - SpaceEvents.applyEvent({ - event, - state: JSON.parse(lastEvent.state), - getVerifiedIdentity, - }), - ); - if (Exit.isFailure(result)) { - console.log('Failed to apply event', result); - throw new Error('Invalid event'); - } - - if (event.transaction.type === 'create-invitation') { - const inviteeAccountAddress = event.transaction.inviteeAccountAddress; - await transaction.invitation.create({ - data: { - id: event.transaction.id, - spaceId, - accountAddress: event.author.accountAddress, - inviteeAccountAddress, - }, - }); - await transaction.spaceKeyBox.createMany({ - data: keyBoxes.map((keyBox) => ({ - id: `${keyBox.id}-${inviteeAccountAddress}`, - nonce: keyBox.nonce, - ciphertext: keyBox.ciphertext, - accountAddress: inviteeAccountAddress, - authorPublicKey: keyBox.authorPublicKey, - spaceKeyId: keyBox.id, - })), - }); - } - if (event.transaction.type === 'accept-invitation') { - await transaction.invitation.delete({ - where: { spaceId_inviteeAccountAddress: { spaceId, inviteeAccountAddress: event.author.accountAddress } }, - }); - - await transaction.space.update({ - where: { id: spaceId }, - data: { members: { connect: { address: event.author.accountAddress } } }, - }); - } - - await transaction.spaceEvent.create({ - data: { - spaceId, - counter: lastEvent.counter + 1, - event: JSON.stringify(event), - id: event.transaction.id, - state: JSON.stringify(result.value), - }, - }); - - if (event.transaction.type === 'create-space-inbox') { - await transaction.spaceInbox.create({ - data: { - id: event.transaction.inboxId, - isPublic: event.transaction.isPublic, - authPolicy: event.transaction.authPolicy, - encryptionPublicKey: event.transaction.encryptionPublicKey, - encryptedSecretKey: event.transaction.secretKey, - space: { connect: { id: spaceId } }, - spaceEvent: { connect: { id: event.transaction.id } }, - }, - }); - } - }); -} diff --git a/apps/server/src/handlers/create-app-identity.ts b/apps/server/src/handlers/create-app-identity.ts deleted file mode 100644 index 6ef2674b..00000000 --- a/apps/server/src/handlers/create-app-identity.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = { - accountAddress: string; - address: string; - appId: string; - ciphertext: string; - signaturePublicKey: string; - encryptionPublicKey: string; - accountProof: string; - keyProof: string; - sessionToken: string; - sessionTokenExpires: Date; -}; - -export const createAppIdentity = async ({ - accountAddress, - address, - appId, - ciphertext, - signaturePublicKey, - encryptionPublicKey, - accountProof, - keyProof, - sessionToken, - sessionTokenExpires, -}: Params) => { - return prisma.$transaction(async (prisma) => { - const existingIdentity = await prisma.appIdentity.findFirst({ - where: { - accountAddress, - appId, - }, - }); - if (existingIdentity) { - throw new Error('App identity already exists'); - } - return await prisma.appIdentity.create({ - data: { - address, - accountAddress, - appId, - ciphertext, - signaturePublicKey, - encryptionPublicKey, - accountProof, - keyProof, - sessionToken, - sessionTokenExpires, - }, - }); - }); -}; diff --git a/apps/server/src/handlers/create-space.ts b/apps/server/src/handlers/create-space.ts deleted file mode 100644 index df1f64f4..00000000 --- a/apps/server/src/handlers/create-space.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { Identity, SpaceEvents } from '@graphprotocol/hypergraph'; -import { Effect, Exit } from 'effect'; - -import { prisma } from '../prisma.js'; -import { getAppOrConnectIdentity } from './getAppOrConnectIdentity.js'; - -type Params = { - accountAddress: string; - event: SpaceEvents.CreateSpaceEvent; - keyBox: Messages.KeyBoxWithKeyId; - infoContent: Uint8Array; - infoSignatureHex: string; - infoSignatureRecovery: number; - name: string; // TODO: remove this field and use infoContent instead -}; - -export const createSpace = async ({ - accountAddress, - event, - keyBox, - infoContent, - infoSignatureHex, - infoSignatureRecovery, - name, -}: Params) => { - const getVerifiedIdentity = (accountAddressToFetch: string, publicKey: string) => { - // applySpaceEvent is only allowed to be called by the account that is applying the event - if (accountAddressToFetch !== accountAddress) { - return Effect.fail(new Identity.InvalidIdentityError()); - } - - return Effect.gen(function* () { - const identity = yield* Effect.tryPromise({ - try: () => getAppOrConnectIdentity({ accountAddress: accountAddressToFetch, signaturePublicKey: publicKey }), - catch: () => new Identity.InvalidIdentityError(), - }); - return identity; - }); - }; - - const result = await Effect.runPromiseExit(SpaceEvents.applyEvent({ event, state: undefined, getVerifiedIdentity })); - if (Exit.isFailure(result)) { - console.error('Create space: Invalid event', result.cause); - throw new Error('Invalid event'); - } - - const keyBoxId = `${keyBox.id}-${accountAddress}`; - - return await prisma.spaceEvent.create({ - data: { - event: JSON.stringify(event), - id: event.transaction.id, - counter: 0, - state: JSON.stringify(result.value), - space: { - create: { - id: event.transaction.id, - infoContent, - infoSignatureHex, - infoSignatureRecovery, - infoAuthorAddress: accountAddress, - name, // TODO: remove this field and use infoContent instead - members: { - connect: { - address: accountAddress, - }, - }, - keys: { - create: { - id: keyBox.id, - keyBoxes: { - create: { - id: keyBoxId, - nonce: keyBox.nonce, - ciphertext: keyBox.ciphertext, - authorPublicKey: keyBox.authorPublicKey, - account: { - connect: { - address: accountAddress, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }); -}; diff --git a/apps/server/src/handlers/createAccountInbox.ts b/apps/server/src/handlers/createAccountInbox.ts deleted file mode 100644 index 94a1d489..00000000 --- a/apps/server/src/handlers/createAccountInbox.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; -export const createAccountInbox = async (data: Messages.RequestCreateAccountInbox) => { - const { accountAddress, inboxId, isPublic, authPolicy, encryptionPublicKey, signature } = data; - // This will throw an error if the inbox already exists - const inbox = await prisma.accountInbox.create({ - data: { - id: inboxId, - isPublic, - authPolicy, - encryptionPublicKey, - signatureHex: signature.hex, - signatureRecovery: signature.recovery, - account: { connect: { address: accountAddress } }, - }, - }); - return inbox; -}; diff --git a/apps/server/src/handlers/createAccountInboxMessage.ts b/apps/server/src/handlers/createAccountInboxMessage.ts deleted file mode 100644 index 4f9a1d02..00000000 --- a/apps/server/src/handlers/createAccountInboxMessage.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; - -type Params = { - accountAddress: string; - inboxId: string; - message: Messages.RequestCreateAccountInboxMessage; -}; - -export const createAccountInboxMessage = async (params: Params): Promise => { - const { accountAddress, inboxId, message } = params; - const accountInbox = await prisma.accountInbox.findUnique({ - where: { - id: inboxId, - accountAddress, - }, - }); - if (!accountInbox) { - throw new Error('Account inbox not found'); - } - - const createdMessage = await prisma.accountInboxMessage.create({ - data: { - ciphertext: message.ciphertext, - signatureHex: message.signature?.hex ?? null, - signatureRecovery: message.signature?.recovery ?? null, - authorAccountAddress: message.authorAccountAddress ?? null, - accountInbox: { - connect: { - id: accountInbox.id, - }, - }, - }, - }); - return { - id: createdMessage.id, - ciphertext: createdMessage.ciphertext, - signature: - createdMessage.signatureHex != null && createdMessage.signatureRecovery != null - ? { - hex: createdMessage.signatureHex, - recovery: createdMessage.signatureRecovery, - } - : undefined, - authorAccountAddress: createdMessage.authorAccountAddress ?? undefined, - createdAt: createdMessage.createdAt, - }; -}; diff --git a/apps/server/src/handlers/createIdentity.ts b/apps/server/src/handlers/createIdentity.ts deleted file mode 100644 index 05f5a7d7..00000000 --- a/apps/server/src/handlers/createIdentity.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = { - signerAddress: string; - accountAddress: string; - ciphertext: string; - nonce: string; - signaturePublicKey: string; - encryptionPublicKey: string; - accountProof: string; - keyProof: string; -}; - -export const createIdentity = async ({ - signerAddress, - accountAddress, - ciphertext, - nonce, - signaturePublicKey, - encryptionPublicKey, - accountProof, - keyProof, -}: Params) => { - // TODO: eventually we may support multiple identities - // for the same account, for now we check there are - // no other identities for the same accountAddress - const existingIdentity = await prisma.account.findFirst({ - where: { - address: accountAddress, - }, - }); - if (existingIdentity) { - throw new Error(`Identity already exists for account ${accountAddress}`); - } - return await prisma.account.create({ - data: { - connectSignerAddress: signerAddress, - address: accountAddress, - connectAccountProof: accountProof, - connectKeyProof: keyProof, - connectSignaturePublicKey: signaturePublicKey, - connectEncryptionPublicKey: encryptionPublicKey, - connectCiphertext: ciphertext, - connectNonce: nonce, - connectAddress: accountAddress, - }, - }); -}; diff --git a/apps/server/src/handlers/createSpaceInboxMessage.ts b/apps/server/src/handlers/createSpaceInboxMessage.ts deleted file mode 100644 index c67c52b1..00000000 --- a/apps/server/src/handlers/createSpaceInboxMessage.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; - -type Params = { - spaceId: string; - inboxId: string; - message: Messages.RequestCreateSpaceInboxMessage; -}; - -export const createSpaceInboxMessage = async (params: Params): Promise => { - const { spaceId, inboxId, message } = params; - const spaceInbox = await prisma.spaceInbox.findUnique({ - where: { - id: inboxId, - }, - }); - if (!spaceInbox) { - throw new Error('Space inbox not found'); - } - if (spaceInbox.spaceId !== spaceId) { - throw new Error('Incorrect space'); - } - const createdMessage = await prisma.spaceInboxMessage.create({ - data: { - spaceInbox: { - connect: { - id: spaceInbox.id, - }, - }, - ciphertext: message.ciphertext, - signatureHex: message.signature?.hex ?? null, - signatureRecovery: message.signature?.recovery ?? null, - authorAccountAddress: message.authorAccountAddress ?? null, - }, - }); - return { - id: createdMessage.id, - ciphertext: createdMessage.ciphertext, - signature: - createdMessage.signatureHex != null && createdMessage.signatureRecovery != null - ? { - hex: createdMessage.signatureHex, - recovery: createdMessage.signatureRecovery, - } - : undefined, - authorAccountAddress: createdMessage.authorAccountAddress ?? undefined, - createdAt: createdMessage.createdAt, - }; -}; diff --git a/apps/server/src/handlers/createUpdate.ts b/apps/server/src/handlers/createUpdate.ts deleted file mode 100644 index 2032051a..00000000 --- a/apps/server/src/handlers/createUpdate.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = { - accountAddress: string; - update: Uint8Array; - spaceId: string; - signatureHex: string; - signatureRecovery: number; - updateId: string; -}; - -export const createUpdate = async ({ - accountAddress, - update, - spaceId, - signatureHex, - signatureRecovery, - updateId, -}: Params) => { - // throw error if account is not a member of the space - await prisma.space.findUniqueOrThrow({ - where: { id: spaceId, members: { some: { address: accountAddress } } }, - }); - - let success = false; - let retries = 0; - const maxRetries = 5; - const retryDelay = 100; // milliseconds - let result: - | { - spaceId: string; - clock: number; - content: Buffer; - } - | undefined; - - while (!success && retries < maxRetries) { - try { - // @ts-expect-error - fix it - result = await prisma.$transaction(async (prisma) => { - const lastUpdate = await prisma.update.findFirst({ - where: { spaceId }, - orderBy: { clock: 'desc' }, - }); - - const clock = lastUpdate ? lastUpdate.clock + 1 : 0; - - return await prisma.update.create({ - data: { - space: { connect: { id: spaceId } }, - clock, - content: Buffer.from(update), - signatureHex, - signatureRecovery, - updateId, - account: { connect: { address: accountAddress } }, - }, - }); - }); - success = true; - } catch (error) { - const dbError = error as { code?: string; message?: string }; - if (dbError.code === 'P2034' || dbError.code === 'P1008' || dbError.message?.includes('database is locked')) { - retries += 1; - console.warn(`Database is busy, retrying (${retries}/${maxRetries})...`); - await new Promise((resolve) => setTimeout(resolve, retryDelay)); - } else { - console.error('Database error:', error); - break; - } - } - } - - if (!result) { - throw new Error('Failed to create update'); - } - - return result; -}; diff --git a/apps/server/src/handlers/find-app-identity.ts b/apps/server/src/handlers/find-app-identity.ts deleted file mode 100644 index 7fe01734..00000000 --- a/apps/server/src/handlers/find-app-identity.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = { - accountAddress: string; - appId: string; -}; - -export const findAppIdentity = async ({ accountAddress, appId }: Params) => { - return await prisma.appIdentity.findFirst({ - where: { - accountAddress, - appId, - }, - }); -}; diff --git a/apps/server/src/handlers/get-app-identity-by-session-token.ts b/apps/server/src/handlers/get-app-identity-by-session-token.ts deleted file mode 100644 index 46053296..00000000 --- a/apps/server/src/handlers/get-app-identity-by-session-token.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { prisma } from '../prisma.js'; - -type GetParams = { - sessionToken: string; -}; - -export const getAppIdentityBySessionToken = async ({ sessionToken }: GetParams) => { - const account = await prisma.appIdentity.findFirst({ - where: { - sessionToken, - }, - select: { - address: true, - sessionTokenExpires: true, - accountAddress: true, - }, - }); - if (!account) { - throw new Error('Account not found'); - } - if (account.sessionTokenExpires && account.sessionTokenExpires < new Date()) { - throw new Error(`Session token expired for account ${account.address}`); - } - return { - address: account.address, - accountAddress: account.accountAddress, - }; -}; diff --git a/apps/server/src/handlers/getAccountInbox.ts b/apps/server/src/handlers/getAccountInbox.ts deleted file mode 100644 index 154170ea..00000000 --- a/apps/server/src/handlers/getAccountInbox.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Inboxes } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; - -export async function getAccountInbox({ accountAddress, inboxId }: { accountAddress: string; inboxId: string }) { - const inbox = await prisma.accountInbox.findUnique({ - where: { id: inboxId, accountAddress }, - select: { - id: true, - account: { - select: { - address: true, - }, - }, - isPublic: true, - authPolicy: true, - encryptionPublicKey: true, - signatureHex: true, - signatureRecovery: true, - }, - }); - if (!inbox) { - throw new Error('Inbox not found'); - } - - return { - inboxId: inbox.id, - accountAddress: inbox.account.address, - isPublic: inbox.isPublic, - authPolicy: inbox.authPolicy as Inboxes.InboxSenderAuthPolicy, - encryptionPublicKey: inbox.encryptionPublicKey, - signature: { - hex: inbox.signatureHex, - recovery: inbox.signatureRecovery, - }, - }; -} diff --git a/apps/server/src/handlers/getAppOrConnectIdentity.ts b/apps/server/src/handlers/getAppOrConnectIdentity.ts deleted file mode 100644 index 31534d06..00000000 --- a/apps/server/src/handlers/getAppOrConnectIdentity.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = - | { - accountAddress: string; - signaturePublicKey: string; - spaceId?: string; - } - | { - accountAddress: string; - appId: string; - spaceId?: string; - }; - -export type GetAppOrConnectIdentityResult = { - accountAddress: string; - ciphertext: string; - nonce?: string; - signaturePublicKey: string; - encryptionPublicKey: string; - accountProof: string; - keyProof: string; - appId: string | null; -}; - -export const getAppOrConnectIdentity = async (params: Params): Promise => { - if (!('appId' in params)) { - const where: { address: string; connectSignaturePublicKey?: string } = { address: params.accountAddress }; - if ('signaturePublicKey' in params) { - where.connectSignaturePublicKey = params.signaturePublicKey; - } - const account = await prisma.account.findFirst({ - where, - }); - if (account) { - return { - accountAddress: account.address, - ciphertext: account.connectCiphertext, - nonce: account.connectNonce, - signaturePublicKey: account.connectSignaturePublicKey, - encryptionPublicKey: account.connectEncryptionPublicKey, - accountProof: account.connectAccountProof, - keyProof: account.connectKeyProof, - appId: null, - }; - } - } - const appWhere: { - accountAddress: string; - appId?: string; - signaturePublicKey?: string; - spaces?: { some: { id: string } }; - } = { - accountAddress: params.accountAddress, - }; - if ('signaturePublicKey' in params) { - appWhere.signaturePublicKey = params.signaturePublicKey; - } - if ('appId' in params) { - appWhere.appId = params.appId; - } - if (params.spaceId) { - appWhere.spaces = { some: { id: params.spaceId } }; - } - - const appIdentity = await prisma.appIdentity.findFirst({ - where: appWhere, - }); - if (appIdentity) { - return { - accountAddress: appIdentity.accountAddress, - ciphertext: appIdentity.ciphertext, - signaturePublicKey: appIdentity.signaturePublicKey, - encryptionPublicKey: appIdentity.encryptionPublicKey, - accountProof: appIdentity.accountProof, - keyProof: appIdentity.keyProof, - appId: appIdentity.appId, - }; - } - throw new Error('Identity not found'); -}; diff --git a/apps/server/src/handlers/getConnectIdentity.ts b/apps/server/src/handlers/getConnectIdentity.ts deleted file mode 100644 index 6c14571a..00000000 --- a/apps/server/src/handlers/getConnectIdentity.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = - | { - accountAddress: string; - connectSignaturePublicKey?: string; - } - | { - accountAddress?: string; - connectSignaturePublicKey: string; - }; - -export type GetConnectIdentityResult = { - accountAddress: string; - ciphertext: string; - nonce: string; - signaturePublicKey: string; - encryptionPublicKey: string; - accountProof: string; - keyProof: string; -}; - -export const getConnectIdentity = async (params: Params): Promise => { - if (!params.accountAddress && !params.connectSignaturePublicKey) { - throw new Error('Either accountAddress or connectSignaturePublicKey must be provided'); - } - const where = params.accountAddress ? { address: params.accountAddress } : params; - const account = await prisma.account.findFirst({ - where, - }); - if (!account) { - throw new Error(`Identity not found for account ${params.accountAddress ?? params.connectSignaturePublicKey}`); - } - return { - accountAddress: account.address, - ciphertext: account.connectCiphertext, - nonce: account.connectNonce, - signaturePublicKey: account.connectSignaturePublicKey, - encryptionPublicKey: account.connectEncryptionPublicKey, - accountProof: account.connectAccountProof, - keyProof: account.connectKeyProof, - }; -}; diff --git a/apps/server/src/handlers/getLatestAccountInboxMessages.ts b/apps/server/src/handlers/getLatestAccountInboxMessages.ts deleted file mode 100644 index 361fd57b..00000000 --- a/apps/server/src/handlers/getLatestAccountInboxMessages.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma.js'; - -interface GetLatestAccountInboxMessagesParams { - inboxId: string; - since: Date; -} - -export async function getLatestAccountInboxMessages({ - inboxId, - since, -}: GetLatestAccountInboxMessagesParams): Promise { - const messages = await prisma.accountInboxMessage.findMany({ - where: { - accountInboxId: inboxId, - createdAt: { - gte: since, - }, - }, - orderBy: { - createdAt: 'asc', - }, - }); - - return messages.map((msg) => ({ - id: msg.id, - ciphertext: msg.ciphertext, - signature: - msg.signatureHex != null && msg.signatureRecovery != null - ? { - hex: msg.signatureHex, - recovery: msg.signatureRecovery, - } - : undefined, - authorAccountAddress: msg.authorAccountAddress ?? undefined, - createdAt: msg.createdAt, - })); -} diff --git a/apps/server/src/handlers/getLatestSpaceInboxMessages.ts b/apps/server/src/handlers/getLatestSpaceInboxMessages.ts deleted file mode 100644 index 59339c10..00000000 --- a/apps/server/src/handlers/getLatestSpaceInboxMessages.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Messages } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma.js'; - -interface GetLatestSpaceInboxMessagesParams { - inboxId: string; - since: Date; -} - -export async function getLatestSpaceInboxMessages({ - inboxId, - since, -}: GetLatestSpaceInboxMessagesParams): Promise { - const messages = await prisma.spaceInboxMessage.findMany({ - where: { - spaceInboxId: inboxId, - createdAt: { - gte: since, - }, - }, - orderBy: { - createdAt: 'asc', - }, - }); - - return messages.map((msg) => ({ - id: msg.id, - ciphertext: msg.ciphertext, - signature: - msg.signatureHex != null && msg.signatureRecovery != null - ? { - hex: msg.signatureHex, - recovery: msg.signatureRecovery, - } - : undefined, - authorAccountAddress: msg.authorAccountAddress ?? undefined, - createdAt: msg.createdAt, - })); -} diff --git a/apps/server/src/handlers/getSpace.ts b/apps/server/src/handlers/getSpace.ts deleted file mode 100644 index 98a15dd5..00000000 --- a/apps/server/src/handlers/getSpace.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { Inboxes } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma.js'; - -type Params = { - spaceId: string; - accountAddress: string; - appIdentityAddress: string; -}; - -export const getSpace = async ({ spaceId, accountAddress, appIdentityAddress }: Params) => { - const space = await prisma.space.findUniqueOrThrow({ - where: { - id: spaceId, - members: { - some: { - address: accountAddress, - }, - }, - }, - include: { - events: { - orderBy: { - counter: 'asc', - }, - }, - keys: { - include: { - keyBoxes: { - where: { - accountAddress, - appIdentityAddress, - }, - select: { - nonce: true, - ciphertext: true, - authorPublicKey: true, - }, - }, - }, - }, - updates: { - orderBy: { - clock: 'asc', - }, - }, - inboxes: { - select: { - id: true, - isPublic: true, - authPolicy: true, - encryptionPublicKey: true, - encryptedSecretKey: true, - }, - }, - }, - }); - - const keyBoxes = space.keys.flatMap((key) => { - return { - id: key.id, - nonce: key.keyBoxes[0].nonce, - ciphertext: key.keyBoxes[0].ciphertext, - accountAddress, - authorPublicKey: key.keyBoxes[0].authorPublicKey, - }; - }); - - return { - id: space.id, - name: space.name, - events: space.events.map((wrapper) => JSON.parse(wrapper.event)), - keyBoxes, - inboxes: space.inboxes.map((inbox) => ({ - inboxId: inbox.id, - isPublic: inbox.isPublic, - authPolicy: inbox.authPolicy as Inboxes.InboxSenderAuthPolicy, - encryptionPublicKey: inbox.encryptionPublicKey, - secretKey: inbox.encryptedSecretKey, - })), - updates: - space.updates.length > 0 - ? { - updates: space.updates.map((update) => ({ - accountAddress: update.accountAddress, - update: new Uint8Array(update.content), - signature: { - hex: update.signatureHex, - recovery: update.signatureRecovery, - }, - updateId: update.updateId, - })), - firstUpdateClock: space.updates[0].clock, - lastUpdateClock: space.updates[space.updates.length - 1].clock, - } - : undefined, - }; -}; diff --git a/apps/server/src/handlers/getSpaceInbox.ts b/apps/server/src/handlers/getSpaceInbox.ts deleted file mode 100644 index 715ef294..00000000 --- a/apps/server/src/handlers/getSpaceInbox.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Inboxes, SpaceEvents } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; - -export async function getSpaceInbox({ spaceId, inboxId }: { spaceId: string; inboxId: string }) { - const inbox = await prisma.spaceInbox.findUnique({ - where: { id: inboxId, spaceId }, - select: { - id: true, - isPublic: true, - authPolicy: true, - encryptionPublicKey: true, - spaceEvent: { - select: { - event: true, - }, - }, - }, - }); - if (!inbox) { - throw new Error('Inbox not found'); - } - - return { - inboxId: inbox.id, - isPublic: inbox.isPublic, - authPolicy: inbox.authPolicy as Inboxes.InboxSenderAuthPolicy, - encryptionPublicKey: inbox.encryptionPublicKey, - creationEvent: JSON.parse(inbox.spaceEvent.event) as SpaceEvents.CreateSpaceInboxEvent, - }; -} diff --git a/apps/server/src/handlers/is-signer-for-account.ts b/apps/server/src/handlers/is-signer-for-account.ts deleted file mode 100644 index 26773bfa..00000000 --- a/apps/server/src/handlers/is-signer-for-account.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { prisma } from '../prisma'; - -export const isSignerForAccount = async (signerAddress: string, accountAddress: string) => { - const account = await prisma.account.findUnique({ - where: { - address: accountAddress, - }, - }); - if (!account) { - return false; - } - return account.connectSignerAddress === signerAddress; -}; diff --git a/apps/server/src/handlers/list-account-inboxes.ts b/apps/server/src/handlers/list-account-inboxes.ts deleted file mode 100644 index 1e73e31d..00000000 --- a/apps/server/src/handlers/list-account-inboxes.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Inboxes } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; - -export async function listAccountInboxes({ accountAddress }: { accountAddress: string }) { - const inboxes = await prisma.accountInbox.findMany({ - where: { accountAddress }, - select: { - id: true, - isPublic: true, - authPolicy: true, - encryptionPublicKey: true, - account: { - select: { - address: true, - }, - }, - signatureHex: true, - signatureRecovery: true, - }, - }); - return inboxes.map((inbox) => { - return { - inboxId: inbox.id, - accountAddress: inbox.account.address, - isPublic: inbox.isPublic, - authPolicy: inbox.authPolicy as Inboxes.InboxSenderAuthPolicy, - encryptionPublicKey: inbox.encryptionPublicKey, - signature: { - hex: inbox.signatureHex, - recovery: inbox.signatureRecovery, - }, - }; - }); -} diff --git a/apps/server/src/handlers/list-public-account-inboxes.ts b/apps/server/src/handlers/list-public-account-inboxes.ts deleted file mode 100644 index 54d9aec2..00000000 --- a/apps/server/src/handlers/list-public-account-inboxes.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Inboxes } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; - -export async function listPublicAccountInboxes({ accountAddress }: { accountAddress: string }) { - const inboxes = await prisma.accountInbox.findMany({ - where: { accountAddress, isPublic: true }, - select: { - id: true, - isPublic: true, - authPolicy: true, - encryptionPublicKey: true, - account: { - select: { - address: true, - }, - }, - signatureHex: true, - signatureRecovery: true, - }, - }); - return inboxes.map((inbox) => { - return { - inboxId: inbox.id, - accountAddress: inbox.account.address, - isPublic: inbox.isPublic, - authPolicy: inbox.authPolicy as Inboxes.InboxSenderAuthPolicy, - encryptionPublicKey: inbox.encryptionPublicKey, - signature: { - hex: inbox.signatureHex, - recovery: inbox.signatureRecovery, - }, - }; - }); -} diff --git a/apps/server/src/handlers/list-spaces-by-account.ts b/apps/server/src/handlers/list-spaces-by-account.ts deleted file mode 100644 index 4c1d209a..00000000 --- a/apps/server/src/handlers/list-spaces-by-account.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = { - accountAddress: string; -}; - -export const listSpacesByAccount = async ({ accountAddress }: Params) => { - return await prisma.space.findMany({ - where: { - members: { - some: { - address: accountAddress, - }, - }, - }, - include: { - appIdentities: { - select: { - address: true, - appId: true, - }, - }, - keys: { - include: { - keyBoxes: { - where: { - accountAddress, - }, - }, - }, - }, - }, - }); -}; diff --git a/apps/server/src/handlers/listInvitations.ts b/apps/server/src/handlers/listInvitations.ts deleted file mode 100644 index eb3ab1a3..00000000 --- a/apps/server/src/handlers/listInvitations.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { SpaceEvents } from '@graphprotocol/hypergraph'; -import { Schema } from 'effect'; - -import { prisma } from '../prisma.js'; - -type Params = { - accountAddress: string; -}; - -const decodeSpaceState = Schema.decodeUnknownEither(SpaceEvents.SpaceState); - -export const listInvitations = async ({ accountAddress }: Params) => { - const result = await prisma.invitation.findMany({ - where: { - inviteeAccountAddress: accountAddress, - }, - include: { - space: { - include: { - events: { - orderBy: { - counter: 'desc', - }, - take: 1, - }, - }, - }, - }, - }); - - return result - .map((invitation) => { - const result = decodeSpaceState(JSON.parse(invitation.space.events[0].state)); - if (result._tag === 'Right') { - const state = result.right; - return { - id: invitation.id, - previousEventHash: state.lastEventHash, - spaceId: invitation.spaceId, - }; - } - console.error('Invalid space state from the DB', result.left); - return null; - }) - .filter((invitation) => invitation !== null); -}; diff --git a/apps/server/src/handlers/listPublicSpaceInboxes.ts b/apps/server/src/handlers/listPublicSpaceInboxes.ts deleted file mode 100644 index 7b23488b..00000000 --- a/apps/server/src/handlers/listPublicSpaceInboxes.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Inboxes, SpaceEvents } from '@graphprotocol/hypergraph'; -import { prisma } from '../prisma'; - -export async function listPublicSpaceInboxes({ spaceId }: { spaceId: string }) { - const inboxes = await prisma.spaceInbox.findMany({ - where: { spaceId, isPublic: true }, - select: { - id: true, - isPublic: true, - authPolicy: true, - encryptionPublicKey: true, - spaceEvent: { - select: { - event: true, - }, - }, - }, - }); - return inboxes.map((inbox) => { - return { - inboxId: inbox.id, - isPublic: inbox.isPublic, - authPolicy: inbox.authPolicy as Inboxes.InboxSenderAuthPolicy, - encryptionPublicKey: inbox.encryptionPublicKey, - creationEvent: JSON.parse(inbox.spaceEvent.event) as SpaceEvents.CreateSpaceInboxEvent, - }; - }); -} diff --git a/apps/server/src/handlers/listSpacesByAppIdentity.ts b/apps/server/src/handlers/listSpacesByAppIdentity.ts deleted file mode 100644 index c648e63c..00000000 --- a/apps/server/src/handlers/listSpacesByAppIdentity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = { - appIdentityAddress: string; -}; - -export const listSpacesByAppIdentity = async ({ appIdentityAddress }: Params) => { - return await prisma.space.findMany({ - where: { - appIdentities: { - some: { - address: appIdentityAddress, - }, - }, - }, - include: { - appIdentities: { - select: { - address: true, - appId: true, - }, - }, - keys: { - include: { - keyBoxes: { - where: { - appIdentityAddress, - }, - }, - }, - }, - }, - }); -}; diff --git a/apps/server/src/handlers/listUpdates.ts b/apps/server/src/handlers/listUpdates.ts deleted file mode 100644 index 0e9780ee..00000000 --- a/apps/server/src/handlers/listUpdates.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { prisma } from '../prisma.js'; - -type Params = { - accountAddress: string; - spaceId: string; - after?: number; -}; - -export const listUpdates = async ({ spaceId, accountAddress, after }: Params) => { - // throw error if account is not a member of the space - await prisma.space.findUniqueOrThrow({ - where: { id: spaceId, members: { some: { address: accountAddress } } }, - }); - - return await prisma.update.findMany({ - where: after - ? { - spaceId, - clock: { gt: after }, - } - : { spaceId }, - orderBy: { - clock: 'desc', - }, - }); -}; diff --git a/apps/server-new/src/http/api.ts b/apps/server/src/http/api.ts similarity index 100% rename from apps/server-new/src/http/api.ts rename to apps/server/src/http/api.ts diff --git a/apps/server-new/src/http/errors.ts b/apps/server/src/http/errors.ts similarity index 100% rename from apps/server-new/src/http/errors.ts rename to apps/server/src/http/errors.ts diff --git a/apps/server-new/src/http/handlers.ts b/apps/server/src/http/handlers.ts similarity index 100% rename from apps/server-new/src/http/handlers.ts rename to apps/server/src/http/handlers.ts diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts old mode 100755 new mode 100644 index d9cbac39..255816b4 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,1079 +1,37 @@ -import { parse } from 'node:url'; -import { Connect, Identity, Inboxes, Messages, SpaceEvents, Utils } from '@graphprotocol/hypergraph'; -import { bytesToHex, randomBytes } from '@noble/hashes/utils.js'; -import cors from 'cors'; -import { Effect, Exit, Schema } from 'effect'; -import express, { type Request } from 'express'; -import WebSocket, { WebSocketServer } from 'ws'; -import { addAppIdentityToSpaces } from './handlers/add-app-identity-to-spaces.js'; -import { applySpaceEvent } from './handlers/applySpaceEvent.js'; -import { createAppIdentity } from './handlers/create-app-identity.js'; -import { createSpace } from './handlers/create-space.js'; -import { createAccountInbox } from './handlers/createAccountInbox.js'; -import { createAccountInboxMessage } from './handlers/createAccountInboxMessage.js'; -import { createIdentity } from './handlers/createIdentity.js'; -import { createSpaceInboxMessage } from './handlers/createSpaceInboxMessage.js'; -import { createUpdate } from './handlers/createUpdate.js'; -import { findAppIdentity } from './handlers/find-app-identity.js'; -import { getAppIdentityBySessionToken } from './handlers/get-app-identity-by-session-token.js'; -import { getAccountInbox } from './handlers/getAccountInbox.js'; -import { type GetAppOrConnectIdentityResult, getAppOrConnectIdentity } from './handlers/getAppOrConnectIdentity.js'; -import { getConnectIdentity } from './handlers/getConnectIdentity.js'; -import { getLatestAccountInboxMessages } from './handlers/getLatestAccountInboxMessages.js'; -import { getLatestSpaceInboxMessages } from './handlers/getLatestSpaceInboxMessages.js'; -import { getSpace } from './handlers/getSpace.js'; -import { getSpaceInbox } from './handlers/getSpaceInbox.js'; -import { isSignerForAccount } from './handlers/is-signer-for-account.js'; -import { listAccountInboxes } from './handlers/list-account-inboxes.js'; -import { listPublicAccountInboxes } from './handlers/list-public-account-inboxes.js'; -import { listSpacesByAccount } from './handlers/list-spaces-by-account.js'; -import { listInvitations } from './handlers/listInvitations.js'; -import { listPublicSpaceInboxes } from './handlers/listPublicSpaceInboxes.js'; -import { listSpacesByAppIdentity } from './handlers/listSpacesByAppIdentity.js'; -import { getAddressByPrivyToken } from './utils/get-address-by-privy-token.js'; - -interface CustomWebSocket extends WebSocket { - accountAddress: string; - appIdentityAddress: string; - subscribedSpaces: Set; -} - -const decodeRequestMessage = Schema.decodeUnknownEither(Messages.RequestMessage); - -const webSocketServer = new WebSocketServer({ noServer: true }); -const PORT = process.env.PORT !== undefined ? Number.parseInt(process.env.PORT, 10) : 3030; -const app = express(); -const CHAIN = process.env.HYPERGRAPH_CHAIN === 'geogenesis' ? Connect.GEOGENESIS : Connect.GEO_TESTNET; -const RPC_URL = process.env.HYPERGRAPH_RPC_URL ?? CHAIN.rpcUrls.default.http[0]; - -app.use(express.json({ limit: '2mb' })); - -app.use(cors()); - -app.get('/', (_req, res) => { - res.send('Server is running (v0.0.14)'); -}); - -app.get('/connect/spaces', async (req, res) => { - console.log('GET connect/spaces'); - try { - const idToken = req.headers['privy-id-token']; - const accountAddress = req.headers['account-address'] as string; - const signerAddress = await getAddressByPrivyToken(idToken); - if (!(await isSignerForAccount(signerAddress, accountAddress))) { - res.status(401).send('Unauthorized'); - return; - } - const spaces = await listSpacesByAccount({ accountAddress }); - const spaceResults = spaces.map((space) => ({ - id: space.id, - infoContent: Utils.bytesToHex(space.infoContent), - infoAuthorAddress: space.infoAuthorAddress, - infoSignatureHex: space.infoSignatureHex, - infoSignatureRecovery: space.infoSignatureRecovery, - name: space.name, // TODO: remove this field and use infoContent instead - appIdentities: space.appIdentities.map((appIdentity) => ({ - appId: appIdentity.appId, - address: appIdentity.address, - })), - keyBoxes: space.keys - .filter((key) => key.keyBoxes.length > 0) - .map((key) => { - return { - id: key.id, - ciphertext: key.keyBoxes[0].ciphertext, - nonce: key.keyBoxes[0].nonce, - authorPublicKey: key.keyBoxes[0].authorPublicKey, - }; - }), - })); - res.status(200).json({ spaces: spaceResults }); - } catch (error) { - console.error('Error listing spaces:', error); - if (error instanceof Error && error.message === 'No Privy ID token provided') { - res.status(401).json({ message: 'Unauthorized' }); - } else if (error instanceof Error && error.message === 'Missing Privy configuration') { - res.status(500).json({ message: 'Internal server error' }); - } else { - res.status(401).json({ message: 'Unauthorized' }); - } - } -}); - -app.post('/connect/spaces', async (req, res) => { - console.log('POST connect/spaces'); - try { - const idToken = req.headers['privy-id-token']; - const message = Schema.decodeUnknownSync(Messages.RequestConnectCreateSpaceEvent)(req.body); - const accountAddress = message.accountAddress; - const signerAddress = await getAddressByPrivyToken(idToken); - if (!(await isSignerForAccount(signerAddress, accountAddress))) { - res.status(401).send('Unauthorized'); - return; - } - const space = await createSpace({ - accountAddress, - event: message.event, - keyBox: message.keyBox, - infoContent: Utils.hexToBytes(message.infoContent), - infoSignatureHex: message.infoSignature.hex, - infoSignatureRecovery: message.infoSignature.recovery, - name: message.name, // TODO: remove this field and use infoContent instead - }); - res.status(200).json({ space }); - } catch (error) { - console.error('Error creating space:', error); - if (error instanceof Error && error.message === 'No Privy ID token provided') { - res.status(401).json({ message: 'Unauthorized' }); - } else if (error instanceof Error && error.message === 'Missing Privy configuration') { - res.status(500).json({ message: 'Internal server error' }); - } else { - res.status(401).json({ message: 'Unauthorized' }); - } - } -}); - -app.post('/connect/add-app-identity-to-spaces', async (req, res) => { - console.log('POST connect/add-app-identity-to-spaces'); - try { - const idToken = req.headers['privy-id-token']; - - const signerAddress = await getAddressByPrivyToken(idToken); - const message = Schema.decodeUnknownSync(Messages.RequestConnectAddAppIdentityToSpaces)(req.body); - if (!(await isSignerForAccount(signerAddress, message.accountAddress))) { - res.status(401).send('Unauthorized'); - return; - } - const space = await addAppIdentityToSpaces({ - accountAddress: message.accountAddress, - appIdentityAddress: message.appIdentityAddress, - spacesInput: message.spacesInput, - }); - res.status(200).json({ space }); - } catch (error) { - console.error('Error adding identity to spaces:', error); - if (error instanceof Error && error.message === 'No Privy ID token provided') { - res.status(401).json({ message: 'Unauthorized' }); - } else if (error instanceof Error && error.message === 'Missing Privy configuration') { - res.status(500).json({ message: 'Internal server error' }); - } else { - res.status(401).json({ message: 'Unauthorized' }); - } - } -}); - -app.post('/connect/identity', async (req, res) => { - console.log('POST connect/identity'); - try { - const idToken = req.headers['privy-id-token']; - const signerAddress = await getAddressByPrivyToken(idToken); - const message = Schema.decodeUnknownSync(Messages.RequestConnectCreateIdentity)(req.body); - const accountAddress = message.keyBox.accountAddress; - - if (signerAddress !== message.keyBox.signer) { - res.status(401).send('Unauthorized'); - return; - } - if ( - !(await Identity.verifyIdentityOwnership( - accountAddress, - message.signaturePublicKey, - message.accountProof, - message.keyProof, - CHAIN, - RPC_URL, - )) - ) { - console.log('Ownership proof is invalid'); - res.status(401).send('Unauthorized'); - return; - } - console.log('Ownership proof is valid'); - - try { - await createIdentity({ - signerAddress, - accountAddress, - ciphertext: message.keyBox.ciphertext, - nonce: message.keyBox.nonce, - signaturePublicKey: message.signaturePublicKey, - encryptionPublicKey: message.encryptionPublicKey, - accountProof: message.accountProof, - keyProof: message.keyProof, - }); - } catch (error) { - console.log('Error creating identity: ', error); - const outgoingMessage: Messages.ResponseIdentityExistsError = { - accountAddress, - }; - res.status(400).send(outgoingMessage); - return; - } - const outgoingMessage: Messages.ResponseConnectCreateIdentity = { - success: true, - }; - res.status(200).send(outgoingMessage); - } catch (error) { - console.error('Error creating identity:', error); - if (error instanceof Error && error.message === 'No Privy ID token provided') { - res.status(401).json({ message: 'Unauthorized' }); - } else if (error instanceof Error && error.message === 'Missing Privy configuration') { - res.status(500).json({ message: 'Internal server error' }); - } else { - res.status(401).json({ message: 'Unauthorized' }); - } - } -}); - -app.get('/connect/identity/encrypted', async (req, res) => { - console.log('GET connect/identity/encrypted'); - try { - const idToken = req.headers['privy-id-token']; - const signerAddress = await getAddressByPrivyToken(idToken); - const accountAddress = req.headers['account-address'] as string; - if (!(await isSignerForAccount(signerAddress, accountAddress))) { - res.status(401).send('Unauthorized'); - return; - } - const identity = await getConnectIdentity({ accountAddress }); - const outgoingMessage: Messages.ResponseIdentityEncrypted = { - keyBox: { - accountAddress, - ciphertext: identity.ciphertext, - nonce: identity.nonce, - signer: signerAddress, +import * as Otlp from '@effect/opentelemetry/Otlp'; +import { FetchHttpClient, PlatformConfigProvider } from '@effect/platform'; +import { NodeContext, NodeRuntime } from '@effect/platform-node'; +import { Effect, Layer, Logger, Option, Redacted } from 'effect'; +import * as Config from './config/honeycomb'; +import { server } from './server'; + +const Observability = Layer.unwrapEffect( + Effect.gen(function* () { + const apiKey = yield* Config.honeycombApiKeyConfig; + if (Option.isNone(apiKey)) { + return Layer.empty; + } + + return Otlp.layer({ + baseUrl: 'https://api.honeycomb.io', + headers: { + 'x-honeycomb-team': Redacted.value(apiKey.value), }, - }; - res.status(200).send(outgoingMessage); - } catch (error) { - console.error('Error creating space:', error); - if (error instanceof Error && error.message === 'No Privy ID token provided') { - res.status(401).json({ message: 'Unauthorized' }); - } else if (error instanceof Error && error.message === 'Missing Privy configuration') { - res.status(500).json({ message: 'Internal server error' }); - } else { - res.status(401).json({ message: 'Unauthorized' }); - } - } -}); - -app.get('/connect/app-identity/:appId', async (req, res) => { - console.log('GET connect/app-identity/:appId'); - try { - const idToken = req.headers['privy-id-token']; - const signerAddress = await getAddressByPrivyToken(idToken); - const accountAddress = req.headers['account-address'] as string; - if (!(await isSignerForAccount(signerAddress, accountAddress))) { - res.status(401).send('Unauthorized'); - return; - } - const appId = req.params.appId; - const appIdentity = await findAppIdentity({ accountAddress, appId }); - if (!appIdentity) { - console.log('App identity not found'); - res.status(404).json({ message: 'App identity not found' }); - return; - } - console.log('App identity found'); - res.status(200).json({ appIdentity }); - } catch (error) { - console.error('Error getting app identity:', error); - if (error instanceof Error && error.message === 'No Privy ID token provided') { - res.status(401).json({ message: 'Unauthorized' }); - } else if (error instanceof Error && error.message === 'Missing Privy configuration') { - res.status(500).json({ message: 'Internal server error' }); - } else { - res.status(401).json({ message: 'Unauthorized' }); - } - } -}); - -app.post('/connect/app-identity', async (req, res) => { - console.log('POST connect/app-identity'); - try { - const idToken = req.headers['privy-id-token']; - const signerAddress = await getAddressByPrivyToken(idToken); - const message = Schema.decodeUnknownSync(Messages.RequestConnectCreateAppIdentity)(req.body); - const accountAddress = message.accountAddress; - if (!(await isSignerForAccount(signerAddress, accountAddress))) { - console.log('Signer address is not the signer for the account'); - res.status(401).send('Unauthorized'); - return; - } - if ( - !Identity.verifyIdentityOwnership( - accountAddress, - message.signaturePublicKey, - message.accountProof, - message.keyProof, - CHAIN, - RPC_URL, - ) - ) { - console.log('Ownership proof is invalid'); - res.status(401).send('Unauthorized'); - return; - } - const sessionToken = bytesToHex(randomBytes(32)); - const sessionTokenExpires = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30); // 30 days - const appIdentity = await createAppIdentity({ - accountAddress, - appId: message.appId, - address: message.address, - ciphertext: message.ciphertext, - signaturePublicKey: message.signaturePublicKey, - encryptionPublicKey: message.encryptionPublicKey, - accountProof: message.accountProof, - keyProof: message.keyProof, - sessionToken, - sessionTokenExpires, - }); - res.status(200).json({ appIdentity }); - } catch (error) { - console.error('Error creating app identity:', error); - if (error instanceof Error && error.message === 'No Privy ID token provided') { - res.status(401).json({ message: 'Unauthorized' }); - } else if (error instanceof Error && error.message === 'Missing Privy configuration') { - res.status(500).json({ message: 'Internal server error' }); - } else { - res.status(401).json({ message: 'Unauthorized' }); - } - } -}); - -app.get('/whoami', async (req, res) => { - console.log('GET whoami'); - try { - const sessionToken = req.headers.authorization?.split(' ')[1]; - if (!sessionToken) { - res.status(401).send('Unauthorized'); - return; - } - try { - const { accountAddress } = await getAppIdentityBySessionToken({ sessionToken }); - res.status(200).send(accountAddress); - } catch (_error) { - res.status(401).send('Unauthorized'); - } - } catch (error) { - console.error('Error getting whoami:', error); - res.status(401).json({ message: 'Unauthorized' }); - } -}); - -app.get('/connect/identity', async (req, res) => { - console.log('GET connect/identity'); - const accountAddress = req.query.accountAddress as string; - if (!accountAddress) { - res.status(400).send('No accountAddress'); - return; - } - try { - const identity = await getConnectIdentity({ accountAddress }); - const outgoingMessage: Messages.ResponseIdentity = { - accountAddress, - signaturePublicKey: identity.signaturePublicKey, - encryptionPublicKey: identity.encryptionPublicKey, - accountProof: identity.accountProof, - keyProof: identity.keyProof, - }; - res.status(200).send(outgoingMessage); - } catch (_error) { - const outgoingMessage: Messages.ResponseIdentityNotFoundError = { - accountAddress, - }; - res.status(404).send(outgoingMessage); - } -}); - -app.get('/identity', async (req, res) => { - console.log('GET identity'); - const accountAddress = req.query.accountAddress as string; - const signaturePublicKey = req.query.signaturePublicKey as string; - const appId = req.query.appId as string; - if (!accountAddress) { - res.status(400).send('No accountAddress'); - return; - } - if (!signaturePublicKey && !appId) { - res.status(400).send('No signaturePublicKey or appId'); - return; - } - try { - const params = signaturePublicKey ? { accountAddress, signaturePublicKey } : { accountAddress, appId }; - const identity = await getAppOrConnectIdentity(params); - const outgoingMessage: Messages.ResponseIdentity = { - accountAddress, - signaturePublicKey: identity.signaturePublicKey, - encryptionPublicKey: identity.encryptionPublicKey, - accountProof: identity.accountProof, - keyProof: identity.keyProof, - appId: identity.appId ?? undefined, - }; - res.status(200).send(outgoingMessage); - } catch (_error) { - const outgoingMessage: Messages.ResponseIdentityNotFoundError = { - accountAddress, - }; - res.status(404).send(outgoingMessage); - } -}); - -app.get('/spaces/:spaceId/inboxes', async (req, res) => { - console.log('GET spaces/:spaceId/inboxes'); - try { - const spaceId = req.params.spaceId; - const inboxes = await listPublicSpaceInboxes({ spaceId }); - const outgoingMessage: Messages.ResponseListSpaceInboxesPublic = { - inboxes, - }; - res.status(200).send(outgoingMessage); - } catch (error) { - console.error('Error getting spaces/:spaceId/inboxes:', error); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -app.get('/spaces/:spaceId/inboxes/:inboxId', async (req, res) => { - console.log('GET spaces/:spaceId/inboxes/:inboxId'); - try { - const spaceId = req.params.spaceId; - const inboxId = req.params.inboxId; - const inbox = await getSpaceInbox({ spaceId, inboxId }); - const outgoingMessage: Messages.ResponseSpaceInboxPublic = { - inbox, - }; - res.status(200).send(outgoingMessage); - } catch (error) { - console.error('Error getting spaces/:spaceId/inboxes/:inboxId:', error); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -app.post('/spaces/:spaceId/inboxes/:inboxId/messages', async (req, res) => { - console.log('POST spaces/:spaceId/inboxes/:inboxId/messages'); - try { - const spaceId = req.params.spaceId; - const inboxId = req.params.inboxId; - const message = Schema.decodeUnknownSync(Messages.RequestCreateSpaceInboxMessage)(req.body); - let spaceInbox: Messages.SpaceInboxPublic; - try { - spaceInbox = await getSpaceInbox({ spaceId, inboxId }); - } catch (_error) { - res.status(404).send({ error: 'Inbox not found' }); - return; - } - - switch (spaceInbox.authPolicy) { - case 'requires_auth': - if (!message.signature || !message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress required' }); - return; - } - break; - case 'anonymous': - if (message.signature || message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress not allowed' }); - return; - } - break; - case 'optional_auth': - if ( - (message.signature && !message.authorAccountAddress) || - (!message.signature && message.authorAccountAddress) - ) { - res.status(400).send({ error: 'Signature and authorAccountAddress must be provided together' }); - return; - } - break; - default: - // This shouldn't happen - res.status(500).send({ error: 'Unknown auth policy' }); - return; - } - - if (message.signature && message.authorAccountAddress) { - // Recover the public key from the signature - const authorPublicKey = Inboxes.recoverSpaceInboxMessageSigner(message, spaceId, inboxId); - - // Check if this public key corresponds to a user's identity - let authorIdentity: GetAppOrConnectIdentityResult; - try { - authorIdentity = await getAppOrConnectIdentity({ - accountAddress: message.authorAccountAddress, - signaturePublicKey: authorPublicKey, - }); - } catch (_error) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; - } - if (authorIdentity.accountAddress !== message.authorAccountAddress) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; - } - } - const createdMessage = await createSpaceInboxMessage({ spaceId, inboxId, message }); - res.status(200).send({}); - broadcastSpaceInboxMessage({ spaceId, inboxId, message: createdMessage }); - } catch (error) { - console.error('Error posting spaces/:spaceId/inboxes/:inboxId/messages:', error); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -app.get('/accounts/:accountAddress/inboxes', async (req, res) => { - console.log('GET accounts/:accountAddress/inboxes'); - try { - const accountAddress = req.params.accountAddress; - const inboxes = await listPublicAccountInboxes({ accountAddress }); - const outgoingMessage: Messages.ResponseListAccountInboxesPublic = { - inboxes, - }; - res.status(200).send(outgoingMessage); - } catch (error) { - console.error('Error getting accounts/:accountAddress/inboxes:', error); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -app.get('/accounts/:accountAddress/inboxes/:inboxId', async (req, res) => { - console.log('GET accounts/:accountAddress/inboxes/:inboxId'); - try { - const accountAddress = req.params.accountAddress; - const inboxId = req.params.inboxId; - const inbox = await getAccountInbox({ accountAddress, inboxId }); - const outgoingMessage: Messages.ResponseAccountInboxPublic = { - inbox, - }; - res.status(200).send(outgoingMessage); - } catch (error) { - console.error('Error getting accounts/:accountAddress/inboxes/:inboxId:', error); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -app.post('/accounts/:accountAddress/inboxes/:inboxId/messages', async (req, res) => { - console.log('POST accounts/:accountAddress/inboxes/:inboxId/messages'); - try { - const accountAddress = req.params.accountAddress; - const inboxId = req.params.inboxId; - const message = Schema.decodeUnknownSync(Messages.RequestCreateAccountInboxMessage)(req.body); - let accountInbox: Messages.AccountInboxPublic; - try { - accountInbox = await getAccountInbox({ accountAddress, inboxId }); - } catch (_error) { - res.status(404).send({ error: 'Inbox not found' }); - return; - } - - switch (accountInbox.authPolicy) { - case 'requires_auth': - if (!message.signature || !message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress required' }); - return; - } - break; - case 'anonymous': - if (message.signature || message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress not allowed' }); - return; - } - break; - case 'optional_auth': - if ( - (message.signature && !message.authorAccountAddress) || - (!message.signature && message.authorAccountAddress) - ) { - res.status(400).send({ error: 'Signature and authorAccountAddress must be provided together' }); - return; - } - break; - default: - // This shouldn't happen - res.status(500).send({ error: 'Unknown auth policy' }); - return; - } - if (message.signature && message.authorAccountAddress) { - // Recover the public key from the signature - const authorPublicKey = Inboxes.recoverAccountInboxMessageSigner(message, accountAddress, inboxId); - - // Check if this public key corresponds to a user's identity - let authorIdentity: GetAppOrConnectIdentityResult; - try { - authorIdentity = await getAppOrConnectIdentity({ - accountAddress: message.authorAccountAddress, - signaturePublicKey: authorPublicKey, - }); - } catch (_error) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; - } - if (authorIdentity.accountAddress !== message.authorAccountAddress) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; - } - } - const createdMessage = await createAccountInboxMessage({ accountAddress, inboxId, message }); - res.status(200).send({}); - broadcastAccountInboxMessage({ accountAddress, inboxId, message: createdMessage }); - } catch (error) { - console.error('Error posting accounts/:accountAddress/inboxes/:inboxId/messages:', error); - res.status(500).json({ message: 'Internal server error' }); - } -}); - -const server = app.listen(PORT, () => { - console.log(`Listening on port ${PORT}`); -}); - -function broadcastSpaceEvents({ - spaceId, - event, - currentClient, -}: { - spaceId: string; - event: SpaceEvents.SpaceEvent; - currentClient: CustomWebSocket; -}) { - try { - for (const client of webSocketServer.clients as Set) { - if (currentClient === client) continue; - - const outgoingMessage: Messages.ResponseSpaceEvent = { - type: 'space-event', - spaceId, - event, - }; - if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { - client.send(Messages.serialize(outgoingMessage)); - } - } - } catch (error) { - console.error('Error broadcasting space events:', error); - } -} - -function broadcastUpdates({ - spaceId, - updates, - currentClient, -}: { - spaceId: string; - updates: Messages.Updates; - currentClient: CustomWebSocket; -}) { - try { - for (const client of webSocketServer.clients as Set) { - if (currentClient === client) continue; - - const outgoingMessage: Messages.ResponseUpdatesNotification = { - type: 'updates-notification', - updates, - spaceId, - }; - if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { - client.send(Messages.serialize(outgoingMessage)); - } - } - } catch (error) { - console.error('Error broadcasting updates:', error); - } -} - -function broadcastSpaceInboxMessage({ - spaceId, - inboxId, - message, -}: { - spaceId: string; - inboxId: string; - message: Messages.InboxMessage; -}) { - try { - const outgoingMessage: Messages.ResponseSpaceInboxMessage = { - type: 'space-inbox-message', - spaceId, - inboxId, - message, - }; - for (const client of webSocketServer.clients as Set) { - if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { - client.send(Messages.serialize(outgoingMessage)); - } - } - } catch (error) { - console.error('Error broadcasting space inbox message:', error); - } -} - -function broadcastAccountInbox({ inbox }: { inbox: Messages.AccountInboxPublic }) { - try { - const outgoingMessage: Messages.ResponseAccountInbox = { - type: 'account-inbox', - inbox, - }; - for (const client of webSocketServer.clients as Set) { - if (client.readyState === WebSocket.OPEN && client.accountAddress === inbox.accountAddress) { - client.send(Messages.serialize(outgoingMessage)); - } - } - } catch (error) { - console.error('Error broadcasting account inbox:', error); - } -} - -function broadcastAccountInboxMessage({ - accountAddress, - inboxId, - message, -}: { - accountAddress: string; - inboxId: string; - message: Messages.InboxMessage; -}) { - try { - const outgoingMessage: Messages.ResponseAccountInboxMessage = { - type: 'account-inbox-message', - accountAddress, - inboxId, - message, - }; - for (const client of webSocketServer.clients as Set) { - if (client.readyState === WebSocket.OPEN && client.accountAddress === accountAddress) { - client.send(Messages.serialize(outgoingMessage)); - } - } - } catch (error) { - console.error('Error broadcasting account inbox message:', error); - } -} - -webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Request) => { - console.log('WS connection'); - const params = parse(request.url, true); - if (!params.query.token || typeof params.query.token !== 'string') { - console.log('No token'); - webSocket.close(); - return; - } - let accountAddress: string; - let appIdentityAddress: string; - try { - const result = await getAppIdentityBySessionToken({ sessionToken: params.query.token }); - accountAddress = result.accountAddress; - webSocket.accountAddress = result.accountAddress; - appIdentityAddress = result.address; - webSocket.appIdentityAddress = result.address; - } catch (_error) { - console.log('Invalid token'); - webSocket.close(); - return; - } - console.log('Account Address:', accountAddress); - webSocket.subscribedSpaces = new Set(); - - webSocket.on('error', (error) => { - console.error('WebSocket error:', error); - }); - - console.log('Connection established', accountAddress); - webSocket.on('message', async (message) => { - console.log('Received websocket message'); - try { - const rawData = Messages.deserialize(message.toString()); - const result = decodeRequestMessage(rawData); - if (result._tag === 'Right') { - const data = result.right; - switch (data.type) { - case 'subscribe-space': { - console.log('--- Received subscribe-space message'); - const space = await getSpace({ accountAddress, spaceId: data.id, appIdentityAddress }); - const outgoingMessage: Messages.ResponseSpace = { - ...space, - type: 'space', - }; - webSocket.subscribedSpaces.add(data.id); - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent subscribe-space response'); - break; - } - case 'list-spaces': { - console.log('--- Received list-spaces message'); - const spaces = await listSpacesByAppIdentity({ appIdentityAddress }); - const outgoingMessage: Messages.ResponseListSpaces = { type: 'list-spaces', spaces: spaces }; - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent list-spaces response'); - break; - } - case 'list-invitations': { - console.log('--- Received list-invitations message'); - const invitations = await listInvitations({ accountAddress }); - const outgoingMessage: Messages.ResponseListInvitations = { - type: 'list-invitations', - invitations: invitations, - }; - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent list-invitations response'); - break; - } - case 'create-space-event': { - console.log('--- Received create-space-event message'); - const getVerifiedIdentity = (accountAddressToFetch: string, publicKey: string) => { - if (accountAddressToFetch !== accountAddress) { - return Effect.fail(new Identity.InvalidIdentityError()); - } - - return Effect.gen(function* () { - const identity = yield* Effect.tryPromise({ - try: () => - getAppOrConnectIdentity({ accountAddress: accountAddressToFetch, signaturePublicKey: publicKey }), - catch: () => new Identity.InvalidIdentityError(), - }); - return identity; - }); - }; - - const applyEventResult = await Effect.runPromiseExit( - SpaceEvents.applyEvent({ - event: data.event, - state: undefined, - getVerifiedIdentity, - }), - ); - if (Exit.isSuccess(applyEventResult)) { - const space = await createSpace({ - accountAddress, - event: data.event, - keyBox: data.keyBox, - infoContent: new Uint8Array(), - infoSignatureHex: '', - infoSignatureRecovery: 0, - name: data.name, - }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: space.id, appIdentityAddress }); - const outgoingMessage: Messages.ResponseSpace = { - ...spaceWithEvents, - type: 'space', - }; - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent create-space-event response'); - } else { - console.log('--- Failed to apply create space event'); - console.log(applyEventResult); - } - // TODO send back error - break; - } - case 'create-invitation-event': { - console.log('--- Received create-invitation-event message'); - await applySpaceEvent({ - accountAddress, - spaceId: data.spaceId, - event: data.event, - keyBoxes: data.keyBoxes.map((keyBox) => keyBox), - }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); - const outgoingMessage: Messages.ResponseSpace = { - ...spaceWithEvents, - type: 'space', - }; - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent create-invitation-event response'); - for (const client of webSocketServer.clients as Set) { - if ( - client.readyState === WebSocket.OPEN && - client.accountAddress === data.event.transaction.inviteeAccountAddress - ) { - const invitations = await listInvitations({ accountAddress: client.accountAddress }); - const outgoingMessage: Messages.ResponseListInvitations = { - type: 'list-invitations', - invitations: invitations, - }; - // for now sending the entire list of invitations to the client - we could send only a single one - client.send(Messages.serialize(outgoingMessage)); - } - } - - broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); - break; - } - case 'accept-invitation-event': { - console.log('--- Received accept-invitation-event message'); - await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); - const outgoingMessage: Messages.ResponseSpace = { - ...spaceWithEvents, - type: 'space', - }; - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent accept-invitation-event response'); - broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); - break; - } - case 'create-space-inbox-event': { - console.log('--- Received create-space-inbox-event message'); - await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); - // TODO send back confirmation instead of the entire space - const outgoingMessage: Messages.ResponseSpace = { - ...spaceWithEvents, - type: 'space', - }; - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent create-space-inbox-event response'); - broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); - break; - } - case 'create-account-inbox': { - console.log('--- Received create-account-inbox message'); - try { - // Check that the signature is valid for the corresponding accountAddress - if (data.accountAddress !== accountAddress) { - throw new Error('Invalid accountAddress'); - } - const signer = Inboxes.recoverAccountInboxCreatorKey(data); - const signerAccount = await getAppOrConnectIdentity({ - accountAddress: data.accountAddress, - signaturePublicKey: signer, - }); - if (signerAccount.accountAddress !== accountAddress) { - throw new Error('Invalid signature'); - } - // Create the inbox (if it doesn't exist) - await createAccountInbox(data); - // Broadcast the inbox to other clients from the same account - broadcastAccountInbox({ inbox: data }); - console.log('--- Broadcasted create-account-inbox'); - } catch (error) { - console.error('--- Error creating account inbox:', error); - return; - } - break; - } - case 'get-latest-space-inbox-messages': { - console.log('--- Received get-latest-space-inbox-messages message'); - try { - // Check that the user has access to this space - await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); - const messages = await getLatestSpaceInboxMessages({ - inboxId: data.inboxId, - since: data.since, - }); - const outgoingMessage: Messages.ResponseSpaceInboxMessages = { - type: 'space-inbox-messages', - spaceId: data.spaceId, - inboxId: data.inboxId, - messages, - }; - webSocket.send(Messages.serialize(outgoingMessage)); - } catch (error) { - console.error('--- Error getting latest space inbox messages:', error); - return; - } - console.log('--- Sent get-latest-space-inbox-messages response'); - break; - } - case 'get-latest-account-inbox-messages': { - console.log('--- Received get-latest-account-inbox-messages message'); - try { - // Check that the user has access to this inbox - await getAccountInbox({ accountAddress, inboxId: data.inboxId }); - const messages = await getLatestAccountInboxMessages({ - inboxId: data.inboxId, - since: data.since, - }); - const outgoingMessage: Messages.ResponseAccountInboxMessages = { - type: 'account-inbox-messages', - accountAddress, - inboxId: data.inboxId, - messages, - }; - webSocket.send(Messages.serialize(outgoingMessage)); - } catch (error) { - console.error('--- Error getting latest account inbox messages:', error); - return; - } - console.log('--- Sent get-latest-account-inbox-messages response'); - break; - } - case 'get-account-inboxes': { - console.log('--- Received get-account-inboxes message'); - const inboxes = await listAccountInboxes({ accountAddress }); - const outgoingMessage: Messages.ResponseAccountInboxes = { - type: 'account-inboxes', - inboxes, - }; - webSocket.send(Messages.serialize(outgoingMessage)); - console.log('--- Sent get-account-inboxes response'); - break; - } - case 'create-update': { - console.log('--- Received create-update message'); - try { - // Check that the update was signed by a valid identity - // belonging to this accountAddress - const signer = Messages.recoverUpdateMessageSigner(data); - const identity = await getAppOrConnectIdentity({ - accountAddress: data.accountAddress, - signaturePublicKey: signer, - }); - if (identity.accountAddress !== accountAddress) { - throw new Error('Invalid signature'); - } - const update = await createUpdate({ - accountAddress, - spaceId: data.spaceId, - update: data.update, - signatureHex: data.signature.hex, - signatureRecovery: data.signature.recovery, - updateId: data.updateId, - }); - const outgoingMessage: Messages.ResponseUpdateConfirmed = { - type: 'update-confirmed', - updateId: data.updateId, - clock: update.clock, - spaceId: data.spaceId, - }; - console.log('--- Sent create-update response'); - webSocket.send(Messages.serialize(outgoingMessage)); - - broadcastUpdates({ - spaceId: data.spaceId, - updates: { - updates: [ - { - accountAddress, - update: data.update, - signature: data.signature, - updateId: data.updateId, - }, - ], - firstUpdateClock: update.clock, - lastUpdateClock: update.clock, - }, - currentClient: webSocket, - }); - } catch (err) { - console.error('--- Error creating update:', err); - } - break; - } - default: - Utils.assertExhaustive(data); - break; - } - } - } catch (error) { - console.error('--- Error processing message:', error); - } - }); - webSocket.on('close', () => { - console.log('Connection closed'); - }); -}); - -server.on('upgrade', async (request, socket, head) => { - webSocketServer.handleUpgrade(request, socket, head, (currentSocket) => { - webSocketServer.emit('connection', currentSocket, request); - }); + resource: { + serviceName: 'hypergraph-server', + }, + }).pipe(Layer.provide(FetchHttpClient.layer)); + }), +); + +const layer = server.pipe( + Layer.provide(Logger.structured), + // Layer.provide(Logger.pretty), + Layer.provide(Observability), + Layer.provide(PlatformConfigProvider.layerDotEnvAdd('.env')), + Layer.provide(NodeContext.layer), +); + +NodeRuntime.runMain(Layer.launch(layer), { + disablePrettyLogger: true, }); diff --git a/apps/server/src/prisma.ts b/apps/server/src/prisma.ts deleted file mode 100644 index dc1fb774..00000000 --- a/apps/server/src/prisma.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaClient } from '../prisma/generated/client/client'; - -export const prisma = new PrismaClient(); diff --git a/apps/server-new/src/server.ts b/apps/server/src/server.ts similarity index 100% rename from apps/server-new/src/server.ts rename to apps/server/src/server.ts diff --git a/apps/server-new/src/services/account-inbox.ts b/apps/server/src/services/account-inbox.ts similarity index 100% rename from apps/server-new/src/services/account-inbox.ts rename to apps/server/src/services/account-inbox.ts diff --git a/apps/server-new/src/services/app-identity.ts b/apps/server/src/services/app-identity.ts similarity index 100% rename from apps/server-new/src/services/app-identity.ts rename to apps/server/src/services/app-identity.ts diff --git a/apps/server-new/src/services/auth.ts b/apps/server/src/services/auth.ts similarity index 100% rename from apps/server-new/src/services/auth.ts rename to apps/server/src/services/auth.ts diff --git a/apps/server-new/src/services/connect-identity.ts b/apps/server/src/services/connect-identity.ts similarity index 100% rename from apps/server-new/src/services/connect-identity.ts rename to apps/server/src/services/connect-identity.ts diff --git a/apps/server-new/src/services/connections.ts b/apps/server/src/services/connections.ts similarity index 100% rename from apps/server-new/src/services/connections.ts rename to apps/server/src/services/connections.ts diff --git a/apps/server-new/src/services/database.ts b/apps/server/src/services/database.ts similarity index 100% rename from apps/server-new/src/services/database.ts rename to apps/server/src/services/database.ts diff --git a/apps/server-new/src/services/identity.ts b/apps/server/src/services/identity.ts similarity index 100% rename from apps/server-new/src/services/identity.ts rename to apps/server/src/services/identity.ts diff --git a/apps/server-new/src/services/invitations.ts b/apps/server/src/services/invitations.ts similarity index 100% rename from apps/server-new/src/services/invitations.ts rename to apps/server/src/services/invitations.ts diff --git a/apps/server-new/src/services/privy-auth.ts b/apps/server/src/services/privy-auth.ts similarity index 100% rename from apps/server-new/src/services/privy-auth.ts rename to apps/server/src/services/privy-auth.ts diff --git a/apps/server-new/src/services/space-inbox.ts b/apps/server/src/services/space-inbox.ts similarity index 100% rename from apps/server-new/src/services/space-inbox.ts rename to apps/server/src/services/space-inbox.ts diff --git a/apps/server-new/src/services/spaces.ts b/apps/server/src/services/spaces.ts similarity index 100% rename from apps/server-new/src/services/spaces.ts rename to apps/server/src/services/spaces.ts diff --git a/apps/server-new/src/services/updates.ts b/apps/server/src/services/updates.ts similarity index 100% rename from apps/server-new/src/services/updates.ts rename to apps/server/src/services/updates.ts diff --git a/apps/server/src/utils/get-address-by-privy-token.ts b/apps/server/src/utils/get-address-by-privy-token.ts deleted file mode 100644 index c2a5d327..00000000 --- a/apps/server/src/utils/get-address-by-privy-token.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { PrivyClient, type Wallet } from '@privy-io/server-auth'; - -export async function getAddressByPrivyToken(idToken: string[] | string | undefined): Promise { - if (!idToken) { - throw new Error('No Privy ID token provided'); - } - - const idTokenString = Array.isArray(idToken) ? idToken[0] : idToken; - - if (!process.env.PRIVY_APP_SECRET || !process.env.PRIVY_APP_ID) { - throw new Error('Missing Privy configuration'); - } - - const privy = new PrivyClient(process.env.PRIVY_APP_ID, process.env.PRIVY_APP_SECRET); - const user = await privy.getUser({ idToken: idTokenString }); - - if (!user) { - throw new Error('Invalid Privy user'); - } - - const wallet = user.linkedAccounts.find( - (account) => account.type === 'wallet' && account.walletClientType === 'privy', - ) as Wallet; - - if (!wallet) { - throw new Error('No Privy wallet found'); - } - - return wallet.address; -} diff --git a/apps/server-new/src/websocket.ts b/apps/server/src/websocket.ts similarity index 100% rename from apps/server-new/src/websocket.ts rename to apps/server/src/websocket.ts diff --git a/apps/server/tsconfig.app.json b/apps/server/tsconfig.app.json deleted file mode 100644 index b2ae238d..00000000 --- a/apps/server/tsconfig.app.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["src", "tsup.config.ts"], - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - "composite": false, - "incremental": false, - "declaration": false, - "declarationMap": false, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "exactOptionalPropertyTypes": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - } -} diff --git a/apps/server/tsconfig.node.json b/apps/server/tsconfig.node.json deleted file mode 100644 index 132ac4b6..00000000 --- a/apps/server/tsconfig.node.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["vite.config.ts"], - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - "composite": false, - "incremental": false, - "declaration": false, - "declarationMap": false, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "exactOptionalPropertyTypes": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - } -} diff --git a/apps/server/tsup.config.ts b/apps/server/tsup.config.ts index 8cc274e1..7ed5a144 100644 --- a/apps/server/tsup.config.ts +++ b/apps/server/tsup.config.ts @@ -3,6 +3,10 @@ import { defineConfig } from 'tsup'; export default defineConfig(() => ({ entry: ['src/index.ts'], format: ['esm'], + target: 'node22', clean: true, sourcemap: true, + minify: false, + splitting: false, + dts: false, })); diff --git a/apps/server-new/vitest.config.ts b/apps/server/vitest.config.ts similarity index 100% rename from apps/server-new/vitest.config.ts rename to apps/server/vitest.config.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8120a8e..1879506d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -292,76 +292,6 @@ importers: version: 5.9.2 apps/server: - dependencies: - '@graphprotocol/hypergraph': - specifier: workspace:* - version: link:../../packages/hypergraph/publish - '@hpke/chacha20poly1305': - specifier: ^1.7.1 - version: 1.7.1 - '@hpke/core': - specifier: ^1.7.4 - version: 1.7.4 - '@noble/ciphers': - specifier: ^1.3.0 - version: 1.3.0 - '@noble/hashes': - specifier: ^1.8.0 - version: 1.8.0 - '@prisma/client': - specifier: ^6.14.0 - version: 6.14.0(prisma@6.14.0(typescript@5.9.2))(typescript@5.9.2) - '@privy-io/server-auth': - specifier: ^1.31.1 - version: 1.31.1(bufferutil@4.0.9)(encoding@0.1.13)(ethers@6.13.5(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.2)(utf-8-validate@5.0.10)(viem@2.34.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)) - body-parser: - specifier: ^2.2.0 - version: 2.2.0 - cors: - specifier: ^2.8.5 - version: 2.8.5 - effect: - specifier: ^3.17.9 - version: 3.17.9 - express: - specifier: ^5.1.0 - version: 5.1.0 - prisma: - specifier: ^6.14.0 - version: 6.14.0(typescript@5.9.2) - siwe: - specifier: ^3.0.0 - version: 3.0.0(ethers@6.13.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - viem: - specifier: ^2.34.0 - version: 2.34.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - ws: - specifier: ^8.18.3 - version: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - devDependencies: - '@types/cors': - specifier: ^2.8.19 - version: 2.8.19 - '@types/express': - specifier: ^5.0.3 - version: 5.0.3 - '@types/node': - specifier: ^24.3.0 - version: 24.3.0 - '@types/pg': - specifier: ^8.15.5 - version: 8.15.5 - '@types/ws': - specifier: ^8.18.1 - version: 8.18.1 - tsup: - specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.11.24(@swc/helpers@0.5.17))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.9.2)(yaml@2.8.1) - typescript: - specifier: ^5.9.2 - version: 5.9.2 - - apps/server-new: dependencies: '@effect/opentelemetry': specifier: ^0.56.4 @@ -3939,17 +3869,6 @@ packages: permissionless: optional: true - '@privy-io/server-auth@1.31.1': - resolution: {integrity: sha512-w0DT0VZCPcXa/Mxqzo7fhXoInX5i4J5BgvzjNsdtMuovgR790kMx/9+K/rSlgtQ/25/B7oDjoIk/f8kd5Ps6mA==} - peerDependencies: - ethers: ^6 - viem: ^2.24.1 - peerDependenciesMeta: - ethers: - optional: true - viem: - optional: true - '@privy-io/server-auth@1.32.0': resolution: {integrity: sha512-X2EaTvRxJy7w4XKt2Tdl+rbbpIFCit8gmz7rFyBQ5T9WeKUFBF3vtBNHz4o8dZxlCgc29yN1hDZV0jVhdeKpVQ==} peerDependencies: @@ -5301,9 +5220,6 @@ packages: '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} - '@types/express@5.0.3': - resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} - '@types/gtag.js@0.0.12': resolution: {integrity: sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==} @@ -5370,9 +5286,6 @@ packages: '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - '@types/node@22.17.2': - resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} - '@types/node@22.18.0': resolution: {integrity: sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==} @@ -5385,9 +5298,6 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/pg@8.15.5': - resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} - '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} @@ -5875,10 +5785,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - acorn-import-phases@1.0.4: resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} engines: {node: '>=10.13.0'} @@ -6231,10 +6137,6 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} - engines: {node: '>=18'} - bonjour-service@1.3.0: resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} @@ -6702,10 +6604,6 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.0.0: - resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} - engines: {node: '>= 0.6'} - content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -6722,10 +6620,6 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} @@ -6997,15 +6891,6 @@ packages: supports-color: optional: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -7654,10 +7539,6 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} - engines: {node: '>= 18'} - exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} @@ -7810,10 +7691,6 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - finalhandler@2.1.0: - resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} - engines: {node: '>= 0.8'} - find-cache-dir@4.0.0: resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} engines: {node: '>=14.16'} @@ -7907,10 +7784,6 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - fs-extra@11.3.1: resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} engines: {node: '>=14.14'} @@ -8593,9 +8466,6 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -9201,10 +9071,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - memfs@3.5.3: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} @@ -9215,10 +9081,6 @@ packages: merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -9381,10 +9243,6 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - mime-db@1.53.0: - resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} - engines: {node: '>= 0.6'} - mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} @@ -9397,14 +9255,6 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime-types@3.0.0: - resolution: {integrity: sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==} - engines: {node: '>= 0.6'} - - mime-types@3.0.1: - resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} - engines: {node: '>= 0.6'} - mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -9550,10 +9400,6 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -9998,10 +9844,6 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@8.2.0: - resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} - engines: {node: '>=16'} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -10038,17 +9880,6 @@ packages: ox: optional: true - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-protocol@1.10.3: - resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -10536,22 +10367,6 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-bytea@1.0.0: - resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - preact@10.24.2: resolution: {integrity: sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==} @@ -10805,10 +10620,6 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - raw-body@3.0.0: - resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} - engines: {node: '>= 0.8'} - rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -11137,10 +10948,6 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - rpc-websockets@9.1.3: resolution: {integrity: sha512-I+kNjW0udB4Fetr3vvtRuYZJS0PcSPyyvBcH5sDdoV8DFs5E4W2pTr7aiMlKfPxANTClP9RlqCPolj9dd5MsEA==} @@ -11256,14 +11063,6 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - send@1.1.0: - resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} - engines: {node: '>= 18'} - - send@1.2.0: - resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} - engines: {node: '>= 18'} - sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} @@ -11291,10 +11090,6 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - serve-static@2.2.0: - resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} - engines: {node: '>= 18'} - set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -11711,9 +11506,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - svix@1.74.1: - resolution: {integrity: sha512-99J8jSsk0viwkoAENVy/zpVoZxwn3kBdUvvpFaWiKjCkkTcqNZdoBqMmariDFceL4Q41ntWfUYxaWD37IAk9Kg==} - svix@1.75.0: resolution: {integrity: sha512-3GLI1DN4Pfd8Ze+7yxoV3Ocp8hW07/WRDsoIBdjDvR916Fx3gRHaYSoa0WWRdKSqJNj+OTCeDSSv47mSp9ibqw==} @@ -12017,10 +11809,6 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -17245,32 +17033,6 @@ snapshots: - utf-8-validate - zod - '@privy-io/server-auth@1.31.1(bufferutil@4.0.9)(encoding@0.1.13)(ethers@6.13.5(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.2)(utf-8-validate@5.0.10)(viem@2.34.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76))': - dependencies: - '@hpke/chacha20poly1305': 1.7.1 - '@hpke/core': 1.7.4 - '@noble/curves': 1.9.7 - '@noble/hashes': 1.8.0 - '@privy-io/public-api': 2.43.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) - '@scure/base': 1.2.6 - '@solana/web3.js': 1.98.4(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2)(utf-8-validate@5.0.10) - canonicalize: 2.1.0 - dotenv: 16.6.1 - jose: 4.15.9 - node-fetch-native: 1.6.7 - redaxios: 0.5.1 - svix: 1.74.1 - ts-case-convert: 2.1.0 - type-fest: 3.13.1 - optionalDependencies: - ethers: 6.13.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) - viem: 2.34.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - transitivePeerDependencies: - - bufferutil - - encoding - - typescript - - utf-8-validate - '@privy-io/server-auth@1.32.0(bufferutil@4.0.9)(encoding@0.1.13)(ethers@6.13.5(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.2)(utf-8-validate@5.0.10)(viem@2.35.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76))': dependencies: '@hpke/chacha20poly1305': 1.7.1 @@ -19041,12 +18803,6 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.8 - '@types/express@5.0.3': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.0.7 - '@types/serve-static': 1.15.8 - '@types/gtag.js@0.0.12': {} '@types/hast@3.0.4': @@ -19105,10 +18861,6 @@ snapshots: '@types/node@17.0.45': {} - '@types/node@22.17.2': - dependencies: - undici-types: 6.21.0 - '@types/node@22.18.0': dependencies: undici-types: 6.21.0 @@ -19123,12 +18875,6 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/pg@8.15.5': - dependencies: - '@types/node': 24.3.0 - pg-protocol: 1.10.3 - pg-types: 2.2.0 - '@types/prismjs@1.26.5': {} '@types/qs@6.14.0': {} @@ -19168,7 +18914,7 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 17.0.45 + '@types/node': 24.3.0 '@types/send@0.17.5': dependencies: @@ -20110,11 +19856,6 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - accepts@2.0.0: - dependencies: - mime-types: 3.0.0 - negotiator: 1.0.0 - acorn-import-phases@1.0.4(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -20524,20 +20265,6 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.2.0: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.0 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.0 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - bonjour-service@1.3.0: dependencies: fast-deep-equal: 3.1.3 @@ -21086,10 +20813,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.0.0: - dependencies: - safe-buffer: 5.2.1 - content-type@1.0.5: {} convert-source-map@1.9.0: {} @@ -21100,8 +20823,6 @@ snapshots: cookie-signature@1.0.6: {} - cookie-signature@1.2.2: {} - cookie@0.7.1: {} copy-text-to-clipboard@3.2.0: {} @@ -21419,10 +21140,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.0: - dependencies: - ms: 2.1.3 - debug@4.4.1: dependencies: ms: 2.1.3 @@ -22281,38 +21998,6 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.1.0: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.0 - content-disposition: 1.0.0 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.2.2 - debug: 4.4.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.0 - fresh: 2.0.0 - http-errors: 2.0.0 - merge-descriptors: 2.0.0 - mime-types: 3.0.0 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.14.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.1.0 - serve-static: 2.2.0 - statuses: 2.0.1 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - exsolve@1.0.7: {} extend-shallow@2.0.1: @@ -22462,17 +22147,6 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.0: - dependencies: - debug: 4.4.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - find-cache-dir@4.0.0: dependencies: common-path-prefix: 3.0.0 @@ -22549,8 +22223,6 @@ snapshots: fresh@0.5.2: {} - fresh@2.0.0: {} - fs-extra@11.3.1: dependencies: graceful-fs: 4.2.11 @@ -23353,8 +23025,6 @@ snapshots: is-potential-custom-element-name@1.0.1: {} - is-promise@4.0.0: {} - is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -24077,8 +23747,6 @@ snapshots: media-typer@0.3.0: {} - media-typer@1.1.0: {} - memfs@3.5.3: dependencies: fs-monkey: 1.1.0 @@ -24087,8 +23755,6 @@ snapshots: merge-descriptors@1.0.3: {} - merge-descriptors@2.0.0: {} - merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -24410,8 +24076,6 @@ snapshots: mime-db@1.52.0: {} - mime-db@1.53.0: {} - mime-db@1.54.0: {} mime-types@2.1.18: @@ -24422,14 +24086,6 @@ snapshots: dependencies: mime-db: 1.52.0 - mime-types@3.0.0: - dependencies: - mime-db: 1.53.0 - - mime-types@3.0.1: - dependencies: - mime-db: 1.54.0 - mime@1.6.0: {} mime@3.0.0: {} @@ -24544,8 +24200,6 @@ snapshots: negotiator@0.6.4: {} - negotiator@1.0.0: {} - neo-async@2.6.2: {} next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): @@ -25111,8 +24765,6 @@ snapshots: path-to-regexp@3.3.0: {} - path-to-regexp@8.2.0: {} - path-type@4.0.0: {} pathe@2.0.3: {} @@ -25161,18 +24813,6 @@ snapshots: optionalDependencies: ox: 0.6.9(typescript@5.9.2)(zod@4.0.17) - pg-int8@1.0.1: {} - - pg-protocol@1.10.3: {} - - pg-types@2.2.0: - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.0 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -25723,16 +25363,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postgres-array@2.0.0: {} - - postgres-bytea@1.0.0: {} - - postgres-date@1.0.7: {} - - postgres-interval@1.2.0: - dependencies: - xtend: 4.0.2 - preact@10.24.2: {} preact@10.27.1: {} @@ -25922,13 +25552,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-body@3.0.0: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - unpipe: 1.0.0 - rc9@2.1.2: dependencies: defu: 6.1.4 @@ -26425,16 +26048,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.47.1 fsevents: 2.3.3 - router@2.2.0: - dependencies: - debug: 4.4.0 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.2.0 - transitivePeerDependencies: - - supports-color - rpc-websockets@9.1.3: dependencies: '@swc/helpers': 0.5.17 @@ -26567,39 +26180,6 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.1.0: - dependencies: - debug: 4.4.0 - destroy: 1.2.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime-types: 2.1.35 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - send@1.2.0: - dependencies: - debug: 4.4.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.0 - mime-types: 3.0.1 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - sentence-case@3.0.4: dependencies: no-case: 3.0.4 @@ -26647,15 +26227,6 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.2.0: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.0 - transitivePeerDependencies: - - supports-color - set-blocking@2.0.0: {} set-cookie-parser@2.7.1: {} @@ -27140,15 +26711,6 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - svix@1.74.1: - dependencies: - '@stablelib/base64': 1.0.1 - '@types/node': 22.17.2 - es6-promise: 4.2.8 - fast-sha256: 1.3.0 - url-parse: 1.5.10 - uuid: 10.0.0 - svix@1.75.0: dependencies: '@stablelib/base64': 1.0.1 @@ -27430,12 +26992,6 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.1 - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 diff --git a/tsconfig.json b/tsconfig.json index 8a8ce949..cbe80ab1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,6 @@ { "path": "packages/hypergraph-react" }, { "path": "packages/typesync-studio" }, { "path": "apps/server" }, - { "path": "apps/server-new" }, { "path": "apps/connect" }, { "path": "apps/events" }, { "path": "apps/template-nextjs" },