From cffebc3149d477f1c8fb04b48e266a352976c286 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Singh <87404569+abhinav29102005@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:04:54 +0530 Subject: [PATCH 1/4] daily check --- package-lock.json | 64 +++++++++++++++++++++++----------------------- package.json | 2 +- prisma/dev.db | Bin 53248 -> 65536 bytes 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 759ca58..863e39e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "devDependencies": { "jest": "^30.0.4", "nodemon": "^3.1.10", - "prisma": "^6.11.1", + "prisma": "^6.12.0", "supertest": "^7.1.3" } }, @@ -1096,9 +1096,9 @@ } }, "node_modules/@prisma/config": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.11.1.tgz", - "integrity": "sha512-z6rCTQN741wxDq82cpdzx2uVykpnQIXalLhrWQSR0jlBVOxCIkz3HZnd8ern3uYTcWKfB3IpVAF7K2FU8t/8AQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.12.0.tgz", + "integrity": "sha512-HovZWzhWEMedHxmjefQBRZa40P81N7/+74khKFz9e1AFjakcIQdXgMWKgt20HaACzY+d1LRBC+L4tiz71t9fkg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -1106,53 +1106,53 @@ } }, "node_modules/@prisma/debug": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.11.1.tgz", - "integrity": "sha512-lWRb/YSWu8l4Yum1UXfGLtqFzZkVS2ygkWYpgkbgMHn9XJlMITIgeMvJyX5GepChzhmxuSuiq/MY/kGFweOpGw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.12.0.tgz", + "integrity": "sha512-plbz6z72orcqr0eeio7zgUrZj5EudZUpAeWkFTA/DDdXEj28YHDXuiakvR6S7sD6tZi+jiwQEJAPeV6J6m/tEQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.11.1.tgz", - "integrity": "sha512-6eKEcV6V8W2eZAUwX2xTktxqPM4vnx3sxz3SDtpZwjHKpC6lhOtc4vtAtFUuf5+eEqBk+dbJ9Dcaj6uQU+FNNg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.12.0.tgz", + "integrity": "sha512-4BRZZUaAuB4p0XhTauxelvFs7IllhPmNLvmla0bO1nkECs8n/o1pUvAVbQ/VOrZR5DnF4HED0PrGai+rIOVePA==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.11.1", - "@prisma/engines-version": "6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9", - "@prisma/fetch-engine": "6.11.1", - "@prisma/get-platform": "6.11.1" + "@prisma/debug": "6.12.0", + "@prisma/engines-version": "6.12.0-15.8047c96bbd92db98a2abc7c9323ce77c02c89dbc", + "@prisma/fetch-engine": "6.12.0", + "@prisma/get-platform": "6.12.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9.tgz", - "integrity": "sha512-swFJTOOg4tHyOM1zB/pHb3MeH0i6t7jFKn5l+ZsB23d9AQACuIRo9MouvuKGvnDogzkcjbWnXi/NvOZ0+n5Jfw==", + "version": "6.12.0-15.8047c96bbd92db98a2abc7c9323ce77c02c89dbc", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.12.0-15.8047c96bbd92db98a2abc7c9323ce77c02c89dbc.tgz", + "integrity": "sha512-70vhecxBJlRr06VfahDzk9ow4k1HIaSfVUT3X0/kZoHCMl9zbabut4gEXAyzJZxaCGi5igAA7SyyfBI//mmkbQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.11.1.tgz", - "integrity": "sha512-NBYzmkXTkj9+LxNPRSndaAeALOL1Gr3tjvgRYNqruIPlZ6/ixLeuE/5boYOewant58tnaYFZ5Ne0jFBPfGXHpQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.12.0.tgz", + "integrity": "sha512-EamoiwrK46rpWaEbLX9aqKDPOd8IyLnZAkiYXFNuq0YsU0Z8K09/rH8S7feOWAVJ3xzeSgcEJtBlVDrajM9Sag==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.11.1", - "@prisma/engines-version": "6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9", - "@prisma/get-platform": "6.11.1" + "@prisma/debug": "6.12.0", + "@prisma/engines-version": "6.12.0-15.8047c96bbd92db98a2abc7c9323ce77c02c89dbc", + "@prisma/get-platform": "6.12.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.11.1.tgz", - "integrity": "sha512-b2Z8oV2gwvdCkFemBTFd0x4lsL4O2jLSx8lB7D+XqoFALOQZPa7eAPE1NU0Mj1V8gPHRxIsHnyUNtw2i92psUw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.12.0.tgz", + "integrity": "sha512-nRerTGhTlgyvcBlyWgt8OLNIV7QgJS2XYXMJD1hysorMCuLAjuDDuoxmVt7C2nLxbuxbWPp7OuFRHC23HqD9dA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.11.1" + "@prisma/debug": "6.12.0" } }, "node_modules/@sinclair/typebox": { @@ -4760,15 +4760,15 @@ } }, "node_modules/prisma": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.11.1.tgz", - "integrity": "sha512-VzJToRlV0s9Vu2bfqHiRJw73hZNCG/AyJeX+kopbu4GATTjTUdEWUteO3p4BLYoHpMS4o8pD3v6tF44BHNZI1w==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.12.0.tgz", + "integrity": "sha512-pmV7NEqQej9WjizN6RSNIwf7Y+jeh9mY1JEX2WjGxJi4YZWexClhde1yz/FuvAM+cTwzchcMytu2m4I6wPkIzg==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.11.1", - "@prisma/engines": "6.11.1" + "@prisma/config": "6.12.0", + "@prisma/engines": "6.12.0" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index a47eb83..f42f3fb 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "devDependencies": { "jest": "^30.0.4", "nodemon": "^3.1.10", - "prisma": "^6.11.1", + "prisma": "^6.12.0", "supertest": "^7.1.3" } } diff --git a/prisma/dev.db b/prisma/dev.db index b8fccf1e6ade2bfe32953b0edecd1b34bb7de252..ad04e409e692f86ca70bbec8a5642c52a8195315 100644 GIT binary patch literal 65536 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 Mwtnl64sEvnAN!J00RR91 delta 1816 zcmeHI%WGU!7{50&W0}dFa}$Koh!q{GD4f!Jp7&faPO@;Lm6CQD)S7WjCzcqLFjllDY)n&T0{&aci~2vMd?b?uDWm`LIu%>I3Z`;0)(}-#Opw z_dV`CtE>6dtGUaMmApHi=asQa-9yj!d3Ux>ZX7vOCd*#+}ESj0= zW>da zGO|Tj`Lpuj;G^K_{*7#K;8K6Kv3xTBcwc$jBX`T&{Vx~S4}Aa7(ZY7&rTj|nr`)Lj z?N+zvch=qrO11Z%+t0S8>+|Oj?UY5jG;dmrvD4M@n8j4`n8u1zDH3X!pi+UxIFB_= zgr?GH%3w-mBEV<^1}98F#W9IxBFeZ-oDt(410}d&f-x#32g6~{VI~+vkrhQT6()w6 z5madCaHXh_h{1tM02N~<0)rDK?TG>dfI>>(h!ZMe0|p`~5I$ncVNDEsg(}Ht0$<8V znnaRn#;H*;)f~x1f@+~?EEOnZ2^NoEtIjoM+NYXl8wG!zznLrTKJ7$iqbKG|oqrvn zy#`muEl`E(2*gk*s!$SmkV(QFE?W=304Y&o9X-42Oj`qV^RE>X?~hIR>-K3Y)>#V(-nZQT1h)Ta-mBvZ#p1xDINPOY zu-jusI1*QuCdiRVP~?JE3p7xnnF2Cf6FX%Z-FMU$I#L~~pbGY{~kZ_Y+(bo}Sy%FPS5CpRy+n`HF-^&rTT z9s6NzghONpSJ0Ov+u4l$ARk`byRmS56c^25cRBq!R!KkZy Date: Mon, 4 Aug 2025 18:01:09 +0530 Subject: [PATCH 2/4] dividing workflow of backend into three different files under src and started working with websockets --- package-lock.json | 542 +++++++++++++++++- package.json | 6 +- prisma/dev.db | Bin 65536 -> 65536 bytes .../migration.sql | 35 ++ prisma/schema.prisma | 51 +- src/app.js | 34 ++ src/config/config.js | 8 + src/config/index.js | 8 - src/index.js | 56 +- src/lib/prisma.js | 3 - src/middlewares/auth.js | 36 +- src/routes/auth.routes.js | 55 +- src/routes/index.js | 86 +++ src/services/jwt.js | 8 +- src/services/prisma.js | 4 +- src/services/websocket.js | 72 +++ 16 files changed, 873 insertions(+), 131 deletions(-) create mode 100644 prisma/migrations/20250804120524_init_v2_schema/migration.sql create mode 100644 src/app.js create mode 100644 src/config/config.js delete mode 100644 src/config/index.js delete mode 100644 src/lib/prisma.js create mode 100644 src/routes/index.js create mode 100644 src/services/websocket.js diff --git a/package-lock.json b/package-lock.json index 863e39e..cd9e8f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,15 @@ "license": "ISC", "dependencies": { "@prisma/client": "^6.11.1", + "@y/websocket-server": "^0.1.1", "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^17.2.0", "express": "^5.1.0", - "jsonwebtoken": "^9.0.2" + "jsonwebtoken": "^9.0.2", + "ws": "^8.18.3", + "y-websocket": "^3.0.0", + "yjs": "^13.6.27" }, "devDependencies": { "jest": "^30.0.4", @@ -1575,6 +1579,63 @@ "win32" ] }, + "node_modules/@y/websocket-server": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@y/websocket-server/-/websocket-server-0.1.1.tgz", + "integrity": "sha512-pPtXm5Ceqs4orhXXHwm2I+u1mKNBDNzlrwNiI7OMwM7PlVS4WCMpiIuSB8WsYeSuISbvpXPNvaj6H1MoQBbE+g==", + "license": "MIT", + "dependencies": { + "lib0": "^0.2.102", + "y-protocols": "^1.0.5" + }, + "bin": { + "y-websocket": "src/server.js", + "y-websocket-server": "src/server.js" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "optionalDependencies": { + "ws": "^6.2.1", + "y-leveldb": "^0.1.0" + }, + "peerDependencies": { + "yjs": "^13.5.6" + } + }, + "node_modules/@y/websocket-server/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "optional": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -1664,6 +1725,13 @@ "dev": true, "license": "MIT" }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "license": "MIT", + "optional": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1776,6 +1844,27 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/bcrypt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", @@ -1890,6 +1979,31 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2349,6 +2463,21 @@ "node": ">=0.10.0" } }, + "node_modules/deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2473,6 +2602,36 @@ "node": ">= 0.8" } }, + "node_modules/encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3134,6 +3293,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -3141,6 +3321,13 @@ "dev": true, "license": "ISC" }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", + "license": "MIT", + "optional": true + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -3297,6 +3484,16 @@ "dev": true, "license": "ISC" }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "license": "MIT", + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -4117,6 +4314,168 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/level": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", + "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", + "license": "MIT", + "optional": true, + "dependencies": { + "level-js": "^5.0.0", + "level-packager": "^5.1.0", + "leveldown": "^5.4.0" + }, + "engines": { + "node": ">=8.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "deprecated": "Superseded by level-transcoder (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "errno": "~0.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-js": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", + "deprecated": "Superseded by browser-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.3", + "buffer": "^5.5.0", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + } + }, + "node_modules/level-packager": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "license": "MIT", + "optional": true, + "dependencies": { + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "deprecated": "Superseded by classic-level (https://github.com/Level/community#faq)", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/leveldown/node_modules/node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/levelup": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "optional": true, + "dependencies": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -4127,6 +4486,27 @@ "node": ">=6" } }, + "node_modules/lib0": { + "version": "0.2.114", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz", + "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==", + "license": "MIT", + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4199,6 +4579,13 @@ "yallist": "^3.0.2" } }, + "node_modules/ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", + "license": "MIT", + "optional": true + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -4359,6 +4746,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "license": "MIT", + "optional": true + }, "node_modules/napi-postinstall": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.2.tgz", @@ -4798,6 +5192,13 @@ "node": ">= 0.10" } }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "license": "MIT", + "optional": true + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -4868,6 +5269,21 @@ "dev": true, "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5192,6 +5608,16 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -5640,6 +6066,13 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -5805,6 +6238,96 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y-leveldb": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/y-leveldb/-/y-leveldb-0.1.2.tgz", + "integrity": "sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg==", + "license": "MIT", + "optional": true, + "dependencies": { + "level": "^6.0.1", + "lib0": "^0.2.31" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-protocols": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz", + "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", + "license": "MIT", + "dependencies": { + "lib0": "^0.2.85" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-websocket": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/y-websocket/-/y-websocket-3.0.0.tgz", + "integrity": "sha512-mUHy7AzkOZ834T/7piqtlA8Yk6AchqKqcrCXjKW8J1w2lPtRDjz8W5/CvXz9higKAHgKRKqpI3T33YkRFLkPtg==", + "license": "MIT", + "dependencies": { + "lib0": "^0.2.102", + "y-protocols": "^1.0.5" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.5.6" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -5896,6 +6419,23 @@ "node": ">=8" } }, + "node_modules/yjs": { + "version": "13.6.27", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", + "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", + "license": "MIT", + "dependencies": { + "lib0": "^0.2.99" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index f42f3fb..d25eeec 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,15 @@ "homepage": "https://github.com/abhinav29102005/eraser-backend#readme", "dependencies": { "@prisma/client": "^6.11.1", + "@y/websocket-server": "^0.1.1", "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^17.2.0", "express": "^5.1.0", - "jsonwebtoken": "^9.0.2" + "jsonwebtoken": "^9.0.2", + "ws": "^8.18.3", + "y-websocket": "^3.0.0", + "yjs": "^13.6.27" }, "devDependencies": { "jest": "^30.0.4", diff --git a/prisma/dev.db b/prisma/dev.db index ad04e409e692f86ca70bbec8a5642c52a8195315..fe55340d4bf4d58ba075662450a56ec4c8053d0a 100644 GIT binary patch delta 1107 zcma)6U1%d!6rM?DGM#B=ZsI08jWyd?t8H16OlC5q1FdPgg03uP{izEJCEm%sW3uF@ zHq&<5{!?5*5Y}FV=>91Bq?BEkxXQjM`Y32&S@c0%5Pa}K_o?({!8?gnd~@J@bM8Hd z!*{-OxSN~Y<|cP(FdSkS=Fs}6AA9FXV<^O=F3ii~`-~v$GV)!}5q8C9@aN#?!8!4s zc<<(VN|3gvB|%kH_F}2?tuV?)6T_%kUaaGW`;rS?Je-(0Rh%Ke_pOnpnDqI+A=kti zD;)`_C*mwqZ6JKU^?I%9;u2hRo85D%ftO3OExb_59C`t8;QNlUVn|Fn!_fmIuJpL) zOpvS60d?3(@?QU&p7RA630l6{C>fMqVIyQgdLkCsS~?=A(I~rJbfI0tUUAPja#K1> zrUGH50$UY~PQzAfxw(KyAyDbul~X>2mwgPFSMDg2@)gn%$E5XBdVT z(S)6IAj%~jsKI1b(@@gF227gJwhhNB=a7kL4%AQvm7!r_OM_Z2iw&gN7P2+X(99hD zn-j=(FvN~yWlf9`&LU{%G%IUcI@@*+eogC|ZfX-+b}*xBrk*WT8&$Woq?cOd3a-O$ zPG9Fryh2HXECYT7H^4=Z1#_KnVi=5%`DfYx!<8p<>5q@eROImjLbMZCc~y_0T|Jp#hg@Fn8xz4-5Vt$J^`24}+O&SGU==hfn#a z7PJ9tW9*;c<-N-^Nbw{~8yI59tqmjnH~Z8G_>ygVD0xZ)g#mwnJ#ZU*2;QQK+u%81 zg2#YDXLny2jrf!7BJCPapNIAN8uldS!v4|j<+O`iu6N@jUl*gC>NiF5!$yQm701YW z@o0R&Z?Y9!t2OCj*U-N+5>&szdTOvdNfhG`dG-D?#Qu-I3hjKxb+}jkmmU=;O+Zbd Z=J_HukGi)g-gCjzoj4ch?CdwhzX0~?JoEqn delta 887 zcmZ9L|4S2L9LMkLZg<<=v(2+>)#&k%)bdLV%z7Nrxx zeyAVRrvk%5=+_|fz#sg@`VSOD{0pKVf)YW3e(7$tlX~F!JTKnQ`+0xh15de3mCMx9 zS+5hrFwfsjn5S>KIe6ZQwLZ%8>;%R!TNu9&3}%bf-OzR9ddaPF`H!=$%xNvBHNdEv zfYE`zzNUqX93x2*z8Ew%nFbskIUhUO*O--#A)Rd@usQUW?MB}noy33-jkxdPUbN}H zK=bntJq&uo{iN;-4hgNgo>B`zH-qZko#+cA59amERH-P#+i^A8ZTD;DIcP4qr0ZE3 zCX}H>RTkF*NLa&@rYb3#s1JANT*+5j7f5#KNW@ z%JE9VNX3r;E8VKTY=SZ6h7wbv5hV`gZYxiQjdCU>H$gQDdz3yUp}-&EClu0ucsx|4JMYxFCwgY%grTWkvlf71@Jpr-{M>}Od4QvuBbU5#) zd7Io7YJcXZT{dwxu{JqvVr+8QMB7B!MA}50BfD5+_E`Bu`+JdvhW`1fQ2|z}v3`Lle1VCfho$=Uix44WKc#5v{BKGqq$Jo}MlwCqwB>scLaZ zWDrNYPin%nNVee9MXjKPW|FylR&B?_EIN#p<0 { + console.error("Global Error Handler:", err.stack); + res.status(err.statusCode || 500).json({ + message: err.message || "Something went wrong!", + error: process.env.NODE_ENV === "development" ? err : {}, + }); +}); + +module.exports = app; \ No newline at end of file diff --git a/src/config/config.js b/src/config/config.js new file mode 100644 index 0000000..4383a05 --- /dev/null +++ b/src/config/config.js @@ -0,0 +1,8 @@ +// --- src/config/config.js --- +// This file centralizes application configuration, loading environment variables. +require('dotenv').config(); + +module.exports = { + PORT: process.env.PORT || 5000, + JWT_SECRET: process.env.JWT_SECRET, +}; \ No newline at end of file diff --git a/src/config/index.js b/src/config/index.js deleted file mode 100644 index 76382f4..0000000 --- a/src/config/index.js +++ /dev/null @@ -1,8 +0,0 @@ - -require('dotenv').config(); - -module.exports = { - PORT: process.env.PORT || 5000, - JWT_SECRET: process.env.JWT_SECRET, - -}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index c506037..1f13bff 100644 --- a/src/index.js +++ b/src/index.js @@ -1,47 +1,43 @@ -// src/index.js +// --- src/index.js --- +// This is the main entry point, responsible for creating and starting the server. +// It keeps the core startup logic clean by delegating to other modules. +const http = require("http"); const express = require("express"); const cors = require("cors"); -const prisma = require('./services/prisma'); // Import the single Prisma instance +const authRoutes = require("./routes/auth.routes"); +const appRoutes = require("./routes"); +const { initializeWebSocketServer } = require("./services/websocket"); const app = express(); +const server = http.createServer(app); +// --- Express API Setup --- app.use(cors({ - origin: ['http://localhost:5173', 'YOUR_PROD_FRONTEND_URL'], // Crucial: ensure frontend URL is allowed - methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], - allowedHeaders: ['Content-Type', 'Authorization'], + origin: ["http://localhost:5173", "YOUR_PROD_FRONTEND_URL"], + methods: ["GET", "POST", "PUT", "PATCH", "DELETE"], + allowedHeaders: ["Content-Type", "Authorization"], })); app.use(express.json()); -// --- Import and Register Routes --- -const authRoutes = require("./routes/auth.routes"); // Public auth routes (NO AUTH MIDDLEWARE HERE) -const appRoutes = require("./routes"); // Your current routes.js, which now contains PROTECTED routes - // Register public auth routes FIRST -app.use("/api/auth", authRoutes); // e.g., /api/auth/test-token is now public +app.use("/api/auth", authRoutes); // Register all other protected app routes -app.use("/api", appRoutes); // e.g., /api/boards, /api/users/me are protected - -// Root route (Health Check) -app.get("/", (req, res) => { - res.send("Eraser v1 backend running"); -}); +app.use("/api", appRoutes); -// Global error handler +// Global Error Handler app.use((err, req, res, next) => { - console.error("Global Error Handler:", err.stack); - res.status(err.statusCode || 500).json({ - message: err.message || 'Something went wrong!', - error: process.env.NODE_ENV === 'development' ? err : {}, - }); + console.error("Global Error Handler:", err.stack); + res.status(err.statusCode || 500).json({ + message: err.message || "Something went wrong!", + error: process.env.NODE_ENV === "development" ? err : {}, + }); }); -// Conditional Server Start -const PORT = process.env.PORT || 5000; -if (process.env.NODE_ENV !== 'test') { - app.listen(PORT, () => { - console.log(`🚀 Server ready at http://localhost:${PORT}`); - }); -} +initializeWebSocketServer(server); -module.exports = app; \ No newline at end of file +const PORT = process.env.PORT || 5000; +server.listen(PORT, () => { + console.log(`🚀 Eraser Backend V2 ready at http://localhost:${PORT}`); + console.log(`⚡ WebSocket server running on port ${PORT}`); +}); \ No newline at end of file diff --git a/src/lib/prisma.js b/src/lib/prisma.js deleted file mode 100644 index e9ae4c4..0000000 --- a/src/lib/prisma.js +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaClient } from '@prisma/client'; -const prisma = new PrismaClient(); -export default prisma; diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js index d9e2883..7e1a54f 100644 --- a/src/middlewares/auth.js +++ b/src/middlewares/auth.js @@ -1,22 +1,20 @@ -const { verifyToken } = require('../services/jwt'); // Correct path to src/services/jwt.js -const prisma = require('../services/prisma'); // Correct path to src/services/prisma.js - +// --- src/middlewares/auth.js --- +// This middleware is responsible for authenticating requests using a JWT. +const { verifyToken } = require("../services/jwt"); +const prisma = require("../services/prisma"); const authMiddleware = async (req, res, next) => { - const authHeader = req.headers.authorization; - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return res.status(401).json({ message: 'No token provided or invalid format' }); - } - const token = authHeader.split(' ')[1]; - try { - const decoded = verifyToken(token); - const user = await prisma.user.findUnique({ where: { id: decoded.userId } }); - if (!user) { return res.status(401).json({ message: 'User not found' }); } - req.user = user; // Attach user object to request - next(); - } catch (error) { - console.error('JWT Verification Error:', error); - if (error.name === 'TokenExpiredError') { return res.status(401).json({ message: 'Token expired' }); } - return res.status(401).json({ message: 'Invalid token' }); - } + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return res.status(401).json({ message: "No token provided or invalid format" });} + const token = authHeader.split(" ")[1]; + try { + const decoded = verifyToken(token); + const user = await prisma.user.findUnique({ where: { id: decoded.userId } }); + if (!user) {return res.status(401).json({ message: "User not found" });} + req.user = user; + next();} catch (error) { + console.error("JWT Verification Error:", error); + if (error.name === "TokenExpiredError") {return res.status(401).json({ message: "Token expired" });} + return res.status(401).json({ message: "Invalid token" });} }; module.exports = authMiddleware; \ No newline at end of file diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js index 9a708fa..e1bee4b 100644 --- a/src/routes/auth.routes.js +++ b/src/routes/auth.routes.js @@ -1,24 +1,24 @@ -// src/routes/auth.routes.js +// --- src/routes/auth.routes.js --- +// This file contains all public authentication routes, including login and registration. +// It is explicitly for unprotected endpoints. const express = require("express"); -const prisma = require("../services/prisma"); // Import prisma -const { generateToken } = require("../services/jwt"); // Import jwt utility +const prisma = require("../services/prisma"); +const { generateToken } = require("../services/jwt"); const bcrypt = require("bcrypt"); const router = express.Router(); -// Temporary route to get a test JWT for local development (UNPROTECTED) -// This is the /api/auth/test-token endpoint router.get("/test-token", async (req, res, next) => { try { - let user = await prisma.user.findFirst(); // Try to find any existing user + let user = await prisma.user.findFirst(); if (!user) { - // If no user exists, create a dummy one for testing + const hashedPassword = await bcrypt.hash("Test1234", 10); user = await prisma.user.create({ data: { email: "testuser@example.com", name: "Test User", - password: "Test123", + hashedPassword, }, }); console.log("Created a dummy user for testing:", user.email); @@ -32,39 +32,34 @@ router.get("/test-token", async (req, res, next) => { }); } catch (error) { console.error("Error generating test token:", error); - next(error); // Pass error to global error handler + res.status(500).json({ message: "Failed to generate test token", error: error.message }); } }); - -//register route -router.post("/register", async (req, res) => { +router.post("/register", async (req, res, next) => { try { - //regex for valid email - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const { name, email, password } = req.body; if (!email || !password) { - return res - .status(400) - .json({ message: "Email and password are required" }); + return res.status(400).json({ message: "Email and password are required" }); } + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { - return res.status(400).json({ message: "Please provide valid email" }); + return res.status(400).json({ message: "Please provide a valid email" }); } + const existingUser = await prisma.user.findUnique({ where: { email } }); if (existingUser) { return res.status(409).json({ message: "Email already registered" }); } - //hash password const hashedPassword = await bcrypt.hash(password, 10); const newUser = await prisma.user.create({ data: { name: name || "New User", email, - password: hashedPassword, + hashedPassword, }, }); @@ -76,26 +71,24 @@ router.post("/register", async (req, res) => { user: { id: newUser.id, email: newUser.email, name: newUser.name }, }); } catch (err) { - console.error("Error registering: ", err); + console.error("Error registering:", err); + res.status(500).json({ message: "Failed to register user" }); } }); - -//login route -router.post("/login", async (req, res) => { +router.post("/login", async (req, res, next) => { try { - const { name, email, password } = req.body; + const { email, password } = req.body; if (!email || !password) { return res.status(401).json({ message: "Please provide credentials" }); } + const user = await prisma.user.findUnique({ where: { email } }); if (!user) { - return res - .status(401) - .json({ message: "Invaild Credentials Email not registered" }); + return res.status(401).json({ message: "Invalid Credentials: Email not registered" }); } - const isPasswordValid = await bcrypt.compare(password, user.password); + const isPasswordValid = await bcrypt.compare(password, user.hashedPassword); if (!isPasswordValid) { return res.status(401).json({ message: "Invalid credentials" }); } @@ -108,8 +101,8 @@ router.post("/login", async (req, res) => { user: { id: user.id, email: user.email, name: user.name }, }); } catch (err) { - console.err("Error: ", err); + console.error("Error during login:", err); + res.status(500).json({ message: "Failed to login" }); } }); - module.exports = router; \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..6a6bf1c --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,86 @@ +// this is src/routes/index.js 2nd pillar of backend design and protected routes management +const express = require("express"); +const prisma = require("../services/prisma"); +const authMiddleware = require("../middlewares/auth"); + +const router = express.Router(); + +router.use(authMiddleware); + +// --- User Routes --- +router.get("/users/me", async (req, res, next) => { + const { id, email, name, createdAt } = req.user; + res.json({ id, email, name, createdAt }); +}); + +// --- Board Routes --- +router.get("/boards", async (req, res, next) => { + try { + const boards = await prisma.board.findMany({ + where: { + userId: req.user.id, + isDeleted: false, + }, + orderBy: { createdAt: "desc" }, + }); + res.json(boards); + } catch (err) { next(err); } +}); + +router.get("/boards/:id", async (req, res, next) => { + try { + const { id } = req.params; + if (!id) { return res.status(400).json({ error: "Please provide board ID" }); } + const board = await prisma.board.findUnique({ + where: { id: id, userId: req.user.id, isDeleted: false }, + }); + if (!board) { return res.status(404).json({ error: "Board not found or not accessible" }); } + res.json(board); + } catch (error) { next(error); } +}); + +router.post("/boards", async (req, res, next) => { + try { + const { name } = req.body; + const boardTitle = name ? name.trim() : "Untitled Board"; + if (!boardTitle) { return res.status(400).json({ error: "Board name cannot be empty" }); } + const board = await prisma.board.create({ + data: { title: boardTitle, userId: req.user.id }, + }); + res.status(201).json({ board }); + } catch (err) { next(err); } +}); + +router.put("/boards/:id", async (req, res, next) => { + try { + const { id } = req.params; + const { name } = req.body; + const boardTitle = name ? name.trim() : ""; + if (!id) { return res.status(400).json({ error: "Please provide board ID" }); } + if (!boardTitle) { return res.status(400).json({ error: "Please provide a valid board name" }); } + const board = await prisma.board.update({ + where: { id: id, userId: req.user.id, isDeleted: false }, + data: { title: boardTitle }, + }); + res.json({ msg: `${id} updated with name: ${boardTitle}`, board }); + } catch (err) { + if (err.code === 'P2025') { return res.status(404).json({ error: "Board not found or not accessible" }); } + next(err); + } +}); + +router.delete("/boards/:id", async (req, res, next) => { + try { + const { id } = req.params; + if (!id) { return res.status(400).json({ error: "Please provide a valid board ID" }); } + const board = await prisma.board.update({ + where: { id: id, userId: req.user.id, isDeleted: false }, + data: { isDeleted: true }, + }); + res.json({ msg: `Board ${id} soft-deleted successfully` }); + } catch (err) { + if (err.code === 'P2025') { return res.status(404).json({ error: "Board not found or not accessible" }); } + next(err); + } +}); +module.exports = router; \ No newline at end of file diff --git a/src/services/jwt.js b/src/services/jwt.js index 9d4d746..f4e707d 100644 --- a/src/services/jwt.js +++ b/src/services/jwt.js @@ -1,5 +1,7 @@ -const jwt = require('jsonwebtoken'); -const config = require('../config'); -const generateToken = (payload) => {return jwt.sign(payload, config.JWT_SECRET, { expiresIn: '7d' });}; +// --- src/services/jwt.js --- +// This utility file contains functions for generating and verifying JWTs. +const jwt = require("jsonwebtoken"); +const config = require("../config/config"); +const generateToken = (payload) => {return jwt.sign(payload, config.JWT_SECRET, { expiresIn: "7d" });}; const verifyToken = (token) => {return jwt.verify(token, config.JWT_SECRET);}; module.exports = { generateToken, verifyToken }; \ No newline at end of file diff --git a/src/services/prisma.js b/src/services/prisma.js index 50bdbb7..de876ea 100644 --- a/src/services/prisma.js +++ b/src/services/prisma.js @@ -1,3 +1,5 @@ +// --- src/services/prisma.js --- +// This file exports a single, shared Prisma client instance for the entire application. const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); -module.exports = prisma; \ No newline at end of file +module.exports = prisma; diff --git a/src/services/websocket.js b/src/services/websocket.js new file mode 100644 index 0000000..5770d7b --- /dev/null +++ b/src/services/websocket.js @@ -0,0 +1,72 @@ +// --- src/services/websocket.js --- +// This module contains all the real-time WebSocket logic, now in the services folder. +const WebSocket = require("ws"); +const { Server } = require("y-websocket"); +const prisma = require("./services/prisma"); +const { Doc } = require("yjs"); +const jwt = require("jsonwebtoken"); +const config = require("./config/config"); + +const docs = new Map(); + +const persistence = { + writeState: async (doc, boardId) => { + try { + const state = Doc.encodeStateAsUpdate(doc); + await prisma.board.update({ + where: { id: boardId }, + data: { documentState: Buffer.from(state) }, + }); + console.log(`[WS] Persisted state for board ${boardId}`); + } catch (error) { + console.error(`[WS] Failed to persist state for board ${boardId}:`, error); + } + }, + + readState: async (boardId) => { + try { + const board = await prisma.board.findUnique({ + where: { id: boardId }, + select: { documentState: true }, + }); + + if (board && board.documentState) { + const doc = new Doc(); + Doc.applyUpdate(doc, new Uint8Array(board.documentState)); + return doc; + } + } catch (error) { + console.error(`[WS] Failed to read state for board ${boardId}:`, error); + } + return new Doc(); + }, +}; + +const initializeWebSocketServer = (server) => { + const wsServer = new Server({ + server: server, + }); + + wsServer.on('connection', (ws, req) => { + const urlParams = new URLSearchParams(req.url.split("?")[1]); + const token = urlParams.get("token"); + const boardId = urlParams.get("boardId"); + + if (!token || !boardId) { + console.log("[WS] Connection rejected: Missing token or boardId."); + ws.close(1008, "Missing token or boardId"); + return; + } + try { + const decoded = jwt.verify(token, config.JWT_SECRET); + console.log(`[WS] Connection authenticated for user ${decoded.userId}`); + } catch (error) { + console.error("[WS] Connection rejected: Invalid token.", error); + ws.close(1008, "Invalid token"); + } + }); + + wsServer.setPersistence(persistence); +}; + +module.exports = { initializeWebSocketServer }; \ No newline at end of file From 5e74adc74056d46edb8927aec48f204a76c96fb0 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Singh <87404569+abhinav29102005@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:04:33 +0530 Subject: [PATCH 3/4] updated file structure and auth file --- src/middlewares/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js index 7e1a54f..9e06ee7 100644 --- a/src/middlewares/auth.js +++ b/src/middlewares/auth.js @@ -1,5 +1,5 @@ // --- src/middlewares/auth.js --- -// This middleware is responsible for authenticating requests using a JWT. + const { verifyToken } = require("../services/jwt"); const prisma = require("../services/prisma"); const authMiddleware = async (req, res, next) => { From 3c8df110c839c359df860af585f99f557f5d625d Mon Sep 17 00:00:00 2001 From: arnav1296 Date: Tue, 12 Aug 2025 01:53:47 +0530 Subject: [PATCH 4/4] feat: Add websocket support and migrate to PostgreSQL Introduces websocket real-time collaboration with new socket handler and config files. Migrates Prisma schema and migrations from SQLite to PostgreSQL, updates dependencies to include pg and socket.io, and adjusts server startup to support websockets. Cleans up old migrations and aligns schema with new database provider. --- docker-compose.yml | 14 + package-lock.json | 492 +++++++++++++++++- package.json | 5 +- .../20250720170023_init/migration.sql | 22 - .../migration.sql | 54 -- .../20250721173603_add_password/migration.sql | 2 - .../migration.sql | 23 - .../20250807065039_init/migration.sql | 49 ++ prisma/migrations/migration_lock.toml | 2 +- prisma/schema.prisma | 8 +- src/index.js | 47 +- websocket/src/boardEvents.js | 0 websocket/src/config.js | 19 + websocket/src/socketHandler.js | 331 ++++++++++++ 14 files changed, 945 insertions(+), 123 deletions(-) create mode 100644 docker-compose.yml delete mode 100644 prisma/migrations/20250720170023_init/migration.sql delete mode 100644 prisma/migrations/20250720170223_init_full_schema/migration.sql delete mode 100644 prisma/migrations/20250721173603_add_password/migration.sql delete mode 100644 prisma/migrations/20250721173710_add_password_to_user/migration.sql create mode 100644 prisma/migrations/20250807065039_init/migration.sql create mode 100644 websocket/src/boardEvents.js create mode 100644 websocket/src/config.js create mode 100644 websocket/src/socketHandler.js diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..187c637 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + postgres: + image: postgres:17.5 + environment: + POSTGRES_USER: eraser_user + POSTGRES_PASSWORD: eraser_pass + POSTGRES_DB: eraser_db + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: diff --git a/package-lock.json b/package-lock.json index 759ca58..930b711 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,10 @@ "cors": "^2.8.5", "dotenv": "^17.2.0", "express": "^5.1.0", - "jsonwebtoken": "^9.0.2" + "jsonwebtoken": "^9.0.2", + "pg": "^8.16.3", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1" }, "devDependencies": { "jest": "^30.0.4", @@ -1182,6 +1185,12 @@ "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", @@ -1238,6 +1247,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1269,7 +1287,6 @@ "version": "24.0.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -1776,6 +1793,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", @@ -2473,6 +2499,125 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4688,6 +4833,95 @@ "node": ">=16" } }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4731,6 +4965,45 @@ "node": ">=8" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pretty-format": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", @@ -5142,6 +5415,173 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5163,6 +5603,15 @@ "source-map": "^0.6.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5562,7 +6011,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -5805,6 +6253,44 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index a47eb83..0ffade2 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,10 @@ "cors": "^2.8.5", "dotenv": "^17.2.0", "express": "^5.1.0", - "jsonwebtoken": "^9.0.2" + "jsonwebtoken": "^9.0.2", + "pg": "^8.16.3", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1" }, "devDependencies": { "jest": "^30.0.4", diff --git a/prisma/migrations/20250720170023_init/migration.sql b/prisma/migrations/20250720170023_init/migration.sql deleted file mode 100644 index 89c74c6..0000000 --- a/prisma/migrations/20250720170023_init/migration.sql +++ /dev/null @@ -1,22 +0,0 @@ --- CreateTable -CREATE TABLE "Stroke" ( - "id" TEXT NOT NULL PRIMARY KEY, - "tool" TEXT NOT NULL, - "color" TEXT NOT NULL, - "strokeWidth" INTEGER NOT NULL, - "points" JSONB NOT NULL, - "user" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "boardId" TEXT NOT NULL, - CONSTRAINT "Stroke_boardId_fkey" FOREIGN KEY ("boardId") REFERENCES "Board" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "Board" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - --- CreateIndex -CREATE INDEX "Stroke_boardId_idx" ON "Stroke"("boardId"); diff --git a/prisma/migrations/20250720170223_init_full_schema/migration.sql b/prisma/migrations/20250720170223_init_full_schema/migration.sql deleted file mode 100644 index c6600f6..0000000 --- a/prisma/migrations/20250720170223_init_full_schema/migration.sql +++ /dev/null @@ -1,54 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `name` on the `Board` table. All the data in the column will be lost. - - You are about to drop the column `user` on the `Stroke` table. All the data in the column will be lost. - - Added the required column `updatedAt` to the `Board` table without a default value. This is not possible if the table is not empty. - - Added the required column `userId` to the `Board` table without a default value. This is not possible if the table is not empty. - - Added the required column `updatedAt` to the `Stroke` table without a default value. This is not possible if the table is not empty. - -*/ --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL PRIMARY KEY, - "email" TEXT NOT NULL, - "name" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Board" ( - "id" TEXT NOT NULL PRIMARY KEY, - "title" TEXT DEFAULT 'Untitled Board', - "isDeleted" BOOLEAN NOT NULL DEFAULT false, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "userId" TEXT NOT NULL, - CONSTRAINT "Board_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Board" ("createdAt", "id") SELECT "createdAt", "id" FROM "Board"; -DROP TABLE "Board"; -ALTER TABLE "new_Board" RENAME TO "Board"; -CREATE TABLE "new_Stroke" ( - "id" TEXT NOT NULL PRIMARY KEY, - "points" JSONB NOT NULL, - "tool" TEXT, - "color" TEXT, - "strokeWidth" INTEGER, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "boardId" TEXT NOT NULL, - CONSTRAINT "Stroke_boardId_fkey" FOREIGN KEY ("boardId") REFERENCES "Board" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); -INSERT INTO "new_Stroke" ("boardId", "color", "createdAt", "id", "points", "strokeWidth", "tool") SELECT "boardId", "color", "createdAt", "id", "points", "strokeWidth", "tool" FROM "Stroke"; -DROP TABLE "Stroke"; -ALTER TABLE "new_Stroke" RENAME TO "Stroke"; -CREATE INDEX "Stroke_boardId_idx" ON "Stroke"("boardId"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/prisma/migrations/20250721173603_add_password/migration.sql b/prisma/migrations/20250721173603_add_password/migration.sql deleted file mode 100644 index df92ba7..0000000 --- a/prisma/migrations/20250721173603_add_password/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "password" TEXT DEFAULT 'changeme' NOT NULL; diff --git a/prisma/migrations/20250721173710_add_password_to_user/migration.sql b/prisma/migrations/20250721173710_add_password_to_user/migration.sql deleted file mode 100644 index 356ab39..0000000 --- a/prisma/migrations/20250721173710_add_password_to_user/migration.sql +++ /dev/null @@ -1,23 +0,0 @@ -/* - Warnings: - - - Made the column `password` on table `User` required. This step will fail if there are existing NULL values in that column. - -*/ --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_User" ( - "id" TEXT NOT NULL PRIMARY KEY, - "email" TEXT NOT NULL, - "password" TEXT NOT NULL, - "name" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); -INSERT INTO "new_User" ("createdAt", "email", "id", "name", "password", "updatedAt") SELECT "createdAt", "email", "id", "name", "password", "updatedAt" FROM "User"; -DROP TABLE "User"; -ALTER TABLE "new_User" RENAME TO "User"; -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/20250807065039_init/migration.sql b/prisma/migrations/20250807065039_init/migration.sql new file mode 100644 index 0000000..db95bde --- /dev/null +++ b/prisma/migrations/20250807065039_init/migration.sql @@ -0,0 +1,49 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "name" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Board" ( + "id" TEXT NOT NULL, + "title" TEXT DEFAULT 'Untitled Board', + "isDeleted" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "userId" TEXT NOT NULL, + + CONSTRAINT "Board_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Stroke" ( + "id" TEXT NOT NULL, + "points" JSONB NOT NULL, + "tool" TEXT, + "color" TEXT, + "strokeWidth" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "boardId" TEXT NOT NULL, + + CONSTRAINT "Stroke_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE INDEX "Stroke_boardId_idx" ON "Stroke"("boardId"); + +-- AddForeignKey +ALTER TABLE "Board" ADD CONSTRAINT "Board_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Stroke" ADD CONSTRAINT "Stroke_boardId_fkey" FOREIGN KEY ("boardId") REFERENCES "Board"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index 2a5a444..044d57c 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (e.g., Git) -provider = "sqlite" +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fe9f9a0..51e2361 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -5,11 +5,11 @@ generator client { } datasource db { - provider = "sqlite" // for local use, or "postgresql" if later switching to Neon + provider = "postgresql" // for local use, or "postgresql" if later switching to Neon url = env("DATABASE_URL") } -// --- NEW/CORRECTED: User Model --- +// User Model model User { id String @id @default(cuid()) // Using cuid() for shorter, URL-safe IDs, as previously discussed. email String @unique // User's email, must be unique @@ -20,7 +20,7 @@ model User { boards Board[] // Relation to Board model: A User can have many Boards } -// --- CORRECTED: Board Model --- +// Board Model model Board { id String @id @default(cuid()) // Using cuid() for shorter, URL-safe IDs title String? @default("Untitled Board") // Using 'title' for consistency and default value @@ -34,7 +34,7 @@ model Board { strokes Stroke[] // Relation to Stroke model: A Board can have many Strokes } -// --- CORRECTED: Stroke Model --- +// Stroke Model model Stroke { id String @id @default(cuid()) // Using cuid() for consistency points Json // Storing XY points as JSON diff --git a/src/index.js b/src/index.js index c506037..09059cd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,30 @@ // src/index.js const express = require("express"); const cors = require("cors"); -const prisma = require('./services/prisma'); // Import the single Prisma instance +const prisma = require("./services/prisma"); // Import the single Prisma instance\ +const { Server } = require("socket.io"); +const { createServer } = require("http"); const app = express(); +const server = createServer(app); -app.use(cors({ - origin: ['http://localhost:5173', 'YOUR_PROD_FRONTEND_URL'], // Crucial: ensure frontend URL is allowed - methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], - allowedHeaders: ['Content-Type', 'Authorization'], -})); +//configure socket.io +const io = new Server(server, { + cors: { + origin: ['http://localhost:5173', 'YOUR_PROD_FRONTEND_URL'], + methods: ['GET', 'POST'], + credentials: true + }, + transports: ['websocket', 'polling'] +}); + +app.use( + cors({ + origin: ["http://localhost:5173", "YOUR_PROD_FRONTEND_URL"], // Crucial: ensure frontend URL is allowed + methods: ["GET", "POST", "PUT", "PATCH", "DELETE"], + allowedHeaders: ["Content-Type", "Authorization"], + }) +); app.use(express.json()); // --- Import and Register Routes --- @@ -27,21 +42,27 @@ app.get("/", (req, res) => { res.send("Eraser v1 backend running"); }); +const socketHandler = require('../websocket/src/socketHandler') ; +io.on('connection', (socket) => { + console.log('User connected', socket.id); + socketHandler(io,socket); +}) + // Global error handler app.use((err, req, res, next) => { - console.error("Global Error Handler:", err.stack); - res.status(err.statusCode || 500).json({ - message: err.message || 'Something went wrong!', - error: process.env.NODE_ENV === 'development' ? err : {}, - }); + console.error("Global Error Handler:", err.stack); + res.status(err.statusCode || 500).json({ + message: err.message || "Something went wrong!", + error: process.env.NODE_ENV === "development" ? err : {}, + }); }); // Conditional Server Start const PORT = process.env.PORT || 5000; -if (process.env.NODE_ENV !== 'test') { +if (process.env.NODE_ENV !== "test") { app.listen(PORT, () => { console.log(`🚀 Server ready at http://localhost:${PORT}`); }); } -module.exports = app; \ No newline at end of file +module.exports = app; diff --git a/websocket/src/boardEvents.js b/websocket/src/boardEvents.js new file mode 100644 index 0000000..e69de29 diff --git a/websocket/src/config.js b/websocket/src/config.js new file mode 100644 index 0000000..3ad891b --- /dev/null +++ b/websocket/src/config.js @@ -0,0 +1,19 @@ +require('dotenv').config(); + +module.exports = { + JWT_SECRET: process.env.JWT_SECRET || 'your-default-secret-key', + WS_PORT: process.env.WS_PORT || 4000, + DATABASE_URL: process.env.DATABASE_URL, + NODE_ENV: process.env.NODE_ENV || 'development', + + // CORS settings for WebSocket + CORS_ORIGIN: process.env.CORS_ORIGIN || 'http://localhost:3001', + + // Yjs specific settings + YJS_GC_ENABLED: process.env.YJS_GC_ENABLED === 'true', + YJS_PERSISTENCE_INTERVAL: parseInt(process.env.YJS_PERSISTENCE_INTERVAL) || 5000, + + // Connection limits + MAX_CONNECTIONS_PER_BOARD: parseInt(process.env.MAX_CONNECTIONS_PER_BOARD) || 50, + CONNECTION_TIMEOUT: parseInt(process.env.CONNECTION_TIMEOUT) || 30000, +}; \ No newline at end of file diff --git a/websocket/src/socketHandler.js b/websocket/src/socketHandler.js new file mode 100644 index 0000000..396dbf0 --- /dev/null +++ b/websocket/src/socketHandler.js @@ -0,0 +1,331 @@ +// websocket/src/sockethandler.js +const prisma = require('../../src/services/prisma'); +const { verifyToken } = require('../../src/services/jwt'); + +// Store active connections per board +const boardConnections = new Map(); // boardId -> Set of socket objects +const userBoards = new Map(); // socketId -> boardId + +const socketHandler = (io, socket) => { + console.log(`User connected: ${socket.id}`); + + +// // Handle authentication +// socket.on('authenticate', async (data) => { +// try { +// const { token } = data; +// if (!token) { +// socket.emit('auth_error', { message: 'Token required' }); +// return; +// } + +// const decoded = verifyToken(token); +// const user = await prisma.user.findUnique({ +// where: { id: decoded.userId } +// }); + +// if (!user) { +// socket.emit('auth_error', { message: 'Invalid token' }); +// return; +// } + +// socket.authenticated = true; +// socket.userId = user.id; +// socket.userEmail = user.email; +// socket.userName = user.name; + +// socket.emit('authenticated', { +// user: { +// id: user.id, +// email: user.email, +// name: user.name +// } +// }); + +// console.log(`User authenticated: ${user.email} (${socket.id})`); +// } catch (error) { +// console.error('Authentication error:', error); +// socket.emit('auth_error', { message: 'Authentication failed' }); +// } +// }); + + // Join a board room for real-time collaboration + socket.on('join_board', async (data) => { + try { + const { boardId } = data; + + if (!boardId) { + socket.emit('error', { message: 'Board ID required' }); + return; + } + + // Verify user has access to this board + const board = await prisma.board.findUnique({ + where: { + id: boardId, + userId: socket.userId, + isDeleted: false + } + }); + + if (!board) { + socket.emit('error', { message: 'Board not found or access denied' }); + return; + } + + // Leave previous board if any + const previousBoard = userBoards.get(socket.id); + if (previousBoard) { + socket.leave(previousBoard); + const prevConnections = boardConnections.get(previousBoard); + if (prevConnections) { + prevConnections.delete(socket); + if (prevConnections.size === 0) { + boardConnections.delete(previousBoard); + } + } + } + + // Join new board + socket.join(boardId); + userBoards.set(socket.id, boardId); + + // Track connection + if (!boardConnections.has(boardId)) { + boardConnections.set(boardId, new Set()); + } + boardConnections.get(boardId).add(socket); + + // Notify others in the board + socket.to(boardId).emit('user_joined', { + userId: socket.userId, + userName: socket.userName, + socketId: socket.id + }); + + // Send current active users to the joining user + const activeUsers = Array.from(boardConnections.get(boardId)) + .filter(s => s.id !== socket.id) + .map(s => ({ + userId: s.userId, + userName: s.userName, + socketId: s.id + })); + + socket.emit('board_joined', { + boardId, + activeUsers, + userCount: boardConnections.get(boardId).size + }); + + console.log(`User ${socket.userEmail} joined board ${boardId}`); + } catch (error) { + console.error('Error joining board:', error); + socket.emit('error', { message: 'Failed to join board' }); + } + }); + + // Handle new stroke creation + socket.on('stroke_start', async (data) => { + try { + const { boardId, stroke } = data; + const currentBoard = userBoards.get(socket.id); + + if (currentBoard !== boardId) { + socket.emit('error', { message: 'Not joined to this board' }); + return; + } + + // Broadcast to other users in the board + socket.to(boardId).emit('stroke_start', { + stroke, + userId: socket.userId, + userName: socket.userName + }); + } catch (error) { + console.error('Error handling stroke start:', error); + } + }); + + // Handle stroke updates (while drawing) + socket.on('stroke_update', async (data) => { + try { + const { boardId, stroke } = data; + const currentBoard = userBoards.get(socket.id); + + if (currentBoard !== boardId) { + socket.emit('error', { message: 'Not joined to this board' }); + return; + } + + // Broadcast to other users in the board + socket.to(boardId).emit('stroke_update', { + stroke, + userId: socket.userId, + userName: socket.userName + }); + } catch (error) { + console.error('Error handling stroke update:', error); + } + }); + + // Handle stroke completion and save to database + socket.on('stroke_end', async (data) => { + try { + const { boardId, stroke } = data; + const currentBoard = userBoards.get(socket.id); + + if (currentBoard !== boardId) { + socket.emit('error', { message: 'Not joined to this board' }); + return; + } + + // Save stroke to database + const savedStroke = await prisma.stroke.create({ + data: { + tool: stroke.tool || 'pen', + color: stroke.color || 'black', + strokeWidth: stroke.strokeWidth || 2, + points: JSON.stringify(stroke.points), + boardId: boardId + } + }); + + // Broadcast to all users in the board (including sender) + io.to(boardId).emit('stroke_saved', { + stroke: { + ...savedStroke, + points: JSON.parse(savedStroke.points) + }, + userId: socket.userId, + userName: socket.userName + }); + } catch (error) { + console.error('Error saving stroke:', error); + socket.emit('error', { message: 'Failed to save stroke' }); + } + }); + + // Handle stroke deletion + socket.on('stroke_delete', async (data) => { + try { + const { boardId, strokeId } = data; + const currentBoard = userBoards.get(socket.id); + + if (currentBoard !== boardId) { + socket.emit('error', { message: 'Not joined to this board' }); + return; + } + + // Delete from database + await prisma.stroke.delete({ + where: { + id: strokeId, + board: { + userId: socket.userId, + isDeleted: false + } + } + }); + + // Broadcast to all users in the board + io.to(boardId).emit('stroke_deleted', { + strokeId, + userId: socket.userId, + userName: socket.userName + }); + } catch (error) { + console.error('Error deleting stroke:', error); + socket.emit('error', { message: 'Failed to delete stroke' }); + } + }); + + // Handle board clear + socket.on('board_clear', async (data) => { + try { + const { boardId } = data; + const currentBoard = userBoards.get(socket.id); + + if (currentBoard !== boardId) { + socket.emit('error', { message: 'Not joined to this board' }); + return; + } + + // Clear all strokes from database + await prisma.stroke.deleteMany({ + where: { + boardId: boardId, + board: { + userId: socket.userId, + isDeleted: false + } + } + }); + + // Broadcast to all users in the board + io.to(boardId).emit('board_cleared', { + userId: socket.userId, + userName: socket.userName + }); + } catch (error) { + console.error('Error clearing board:', error); + socket.emit('error', { message: 'Failed to clear board' }); + } + }); + + // Handle cursor movement for showing other users' cursors + socket.on('cursor_move', (data) => { + try { + const { boardId, x, y } = data; + const currentBoard = userBoards.get(socket.id); + + if (currentBoard !== boardId) { + return; + } + + // Broadcast cursor position to other users + socket.to(boardId).emit('cursor_moved', { + userId: socket.userId, + userName: socket.userName, + x, + y + }); + } catch (error) { + console.error('Error handling cursor move:', error); + } + }); + + + // Handle disconnection + socket.on('disconnect', () => { + console.log(`User disconnected: ${socket.id}`); + + const boardId = userBoards.get(socket.id); + if (boardId) { + // Remove from board connections + const connections = boardConnections.get(boardId); + if (connections) { + connections.delete(socket); + if (connections.size === 0) { + boardConnections.delete(boardId); + } + } + + // Notify other users in the board + socket.to(boardId).emit('user_left', { + userId: socket.userId, + userName: socket.userName, + socketId: socket.id + }); + } + + userBoards.delete(socket.id); + }); + + // Handle connection errors + socket.on('error', (error) => { + console.error(`Socket error for ${socket.id}:`, error); + }); +}; + +module.exports = socketHandler; \ No newline at end of file