From 3a4bf102aaf9fae6887e8ef430fd42f73734e6cf Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:44:02 +0200 Subject: [PATCH 01/25] feat(azure-deploy): add workflow for deploying backend to Azure Web App --- .github/workflows/azure-deploy.yml | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/azure-deploy.yml diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml new file mode 100644 index 00000000..8ea7afc9 --- /dev/null +++ b/.github/workflows/azure-deploy.yml @@ -0,0 +1,48 @@ +name: Deploy Backend to Azure Web App + +on: + push: + branches: + - main + - add-azure-deployment + paths: + - "server/**" + - ".github/workflows/azure-deploy.yml" + workflow_dispatch: + +env: + AZURE_WEBAPP_NAME: ${{ secrets.AZURE_WEBAPP_NAME }} + AZURE_WEBAPP_PACKAGE_PATH: "./server" + NODE_VERSION: "24.x" + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: "npm" + cache-dependency-path: server/package-lock.json + + - name: Install dependencies + run: | + cd server + npm ci --omit=dev + + - name: Create .env file placeholder + run: | + cd server + echo "# Environment variables will be set in Azure App Settings" > .env + + - name: Deploy to Azure Web App + uses: azure/webapps-deploy@v3 + with: + app-name: ${{ secrets.AZURE_WEBAPP_NAME }} + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} From c8f97c53c512afe927e355208ee3d3f0eb6f33fa Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:45:42 +0200 Subject: [PATCH 02/25] Update branch name for Azure deployment workflow --- .github/workflows/azure-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index 8ea7afc9..016686cb 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - add-azure-deployment + - feat/azure-deploy paths: - "server/**" - ".github/workflows/azure-deploy.yml" From e053c552763dd1c1abb4c151833ab93bbf521da0 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:02:31 +0200 Subject: [PATCH 03/25] chore: add consola to dependencies for Azure --- server/package-lock.json | 753 +++++++++++++++++++++++++++++++++++++++ server/package.json | 2 + 2 files changed, 755 insertions(+) diff --git a/server/package-lock.json b/server/package-lock.json index e0c5d9e9..8d8992c0 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -14,6 +14,7 @@ "axios": "^1.12.2", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", + "consola": "^2.15.3", "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.6.1", @@ -22,6 +23,7 @@ "express-session": "^1.18.2", "firebase": "^12.3.0", "firebase-admin": "^13.5.0", + "googleapis": "^169.0.0", "joi": "^17.13.1", "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", @@ -865,6 +867,102 @@ "node": ">=18" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -895,6 +993,16 @@ "node": ">=8.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2072,6 +2180,12 @@ "typedarray": "^0.0.6" } }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2152,6 +2266,20 @@ "node": ">= 10" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crypto-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", @@ -2165,6 +2293,15 @@ "dev": true, "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/date-fns": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", @@ -2358,6 +2495,12 @@ "stream-shift": "^1.0.2" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -2704,6 +2847,29 @@ "node": ">=0.8.0" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2883,6 +3049,22 @@ "unicode-trie": "^2.0.0" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", @@ -2901,6 +3083,18 @@ "node": ">= 0.12" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3234,6 +3428,369 @@ "node": ">=14" } }, + "node_modules/googleapis": { + "version": "169.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-169.0.0.tgz", + "integrity": "sha512-IOGMG8tljCZSLvYgdojRu6mB10KEsK0J7X62sXXlQz9koe5BUAW+rqkY3qhQM9wXM6hVL3/Hase7XbxoMyeYiQ==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.2.0", + "googleapis-common": "^8.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.1.tgz", + "integrity": "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^7.0.0-rc.4", + "google-auth-library": "^10.1.0", + "qs": "^6.7.0", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/googleapis-common/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/googleapis-common/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common/node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/googleapis-common/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/googleapis-common/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/googleapis/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/googleapis/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/googleapis/node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis/node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis/node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/googleapis/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/googleapis/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3583,6 +4140,27 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/joi": { "version": "17.13.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", @@ -4112,6 +4690,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -4344,6 +4931,26 @@ "node": ">=6.0.0" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -4648,6 +5255,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -4681,6 +5294,37 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -5369,6 +6013,27 @@ "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "license": "MIT" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -5447,6 +6112,18 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -5603,6 +6280,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5615,6 +6307,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -5992,6 +6697,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6029,6 +6740,15 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/web-vitals": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", @@ -6080,6 +6800,21 @@ "node": ">=18" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", @@ -6121,6 +6856,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/server/package.json b/server/package.json index 4c28f464..aaacc14c 100644 --- a/server/package.json +++ b/server/package.json @@ -18,6 +18,7 @@ "axios": "^1.12.2", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", + "consola": "^2.15.3", "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.6.1", @@ -26,6 +27,7 @@ "express-session": "^1.18.2", "firebase": "^12.3.0", "firebase-admin": "^13.5.0", + "googleapis": "^169.0.0", "joi": "^17.13.1", "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", From 637017a6db5eec10e79b8759bd719d520c8c28fa Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:53:02 +0200 Subject: [PATCH 04/25] fix: update Node.js version for Azure deployment and improve logger import --- .github/workflows/azure-deploy.yml | 8 ++++---- server/src/utils/logger.js | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index 016686cb..bcce0277 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -12,8 +12,8 @@ on: env: AZURE_WEBAPP_NAME: ${{ secrets.AZURE_WEBAPP_NAME }} - AZURE_WEBAPP_PACKAGE_PATH: "./server" - NODE_VERSION: "24.x" + AZURE_WEBAPP_PACKAGE_PATH: "./server" + NODE_VERSION: "22.x" # Using current stable LTS for better compatibility jobs: build-and-deploy: @@ -38,11 +38,11 @@ jobs: - name: Create .env file placeholder run: | cd server - echo "# Environment variables will be set in Azure App Settings" > .env + echo "# Env vars managed in Azure Portal" > .env - name: Deploy to Azure Web App uses: azure/webapps-deploy@v3 with: app-name: ${{ secrets.AZURE_WEBAPP_NAME }} publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} + package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} \ No newline at end of file diff --git a/server/src/utils/logger.js b/server/src/utils/logger.js index 61cc76a7..1f695015 100644 --- a/server/src/utils/logger.js +++ b/server/src/utils/logger.js @@ -16,7 +16,8 @@ * - apiLogger, authLogger, dbLogger, emailLogger, paymentLogger */ -import { createConsola } from "consola"; +import consolaPkg from "consola"; +const { createConsola } = consolaPkg; const isDevelopment = process.env.NODE_ENV === "development"; const isProduction = process.env.NODE_ENV === "production"; From 18ae5002b9cc1c9656e52f19a2f08ad6904298a0 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:03:38 +0200 Subject: [PATCH 05/25] fix: update logger import to use default export from consola --- server/package-lock.json | 13 ++++++++----- server/package.json | 2 +- server/src/index.js | 2 +- server/src/utils/logger.js | 3 +-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 8d8992c0..99796f4d 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -14,7 +14,7 @@ "axios": "^1.12.2", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", - "consola": "^2.15.3", + "consola": "^3.4.2", "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.6.1", @@ -2181,10 +2181,13 @@ } }, "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", - "license": "MIT" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "node_modules/content-disposition": { "version": "0.5.4", diff --git a/server/package.json b/server/package.json index aaacc14c..aa3d3db9 100644 --- a/server/package.json +++ b/server/package.json @@ -18,7 +18,7 @@ "axios": "^1.12.2", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", - "consola": "^2.15.3", + "consola": "^3.4.2", "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.6.1", diff --git a/server/src/index.js b/server/src/index.js index e7dcdf06..8aedf38e 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -6,7 +6,7 @@ import { startReminderScheduler } from "./features/events/event.service.js"; import { initCertificateScheduler } from "./utils/certificate-scheduler.js"; import Feedback from "./features/feedback/feedback.model.js"; -const PORT = process.env.PORT || 5000; +const PORT = process.env.PORT || 8080; const startServer = async () => { // Connect to the database before starting the server diff --git a/server/src/utils/logger.js b/server/src/utils/logger.js index 1f695015..61cc76a7 100644 --- a/server/src/utils/logger.js +++ b/server/src/utils/logger.js @@ -16,8 +16,7 @@ * - apiLogger, authLogger, dbLogger, emailLogger, paymentLogger */ -import consolaPkg from "consola"; -const { createConsola } = consolaPkg; +import { createConsola } from "consola"; const isDevelopment = process.env.NODE_ENV === "development"; const isProduction = process.env.NODE_ENV === "production"; From 7dbe7eab5ae6cf640511b76d8a06b0eb964fc73e Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:54:40 +0200 Subject: [PATCH 06/25] fix: update Azure deployment workflow and improve CORS configuration in app.js --- .github/workflows/azure-deploy.yml | 13 ++++--------- server/src/app.js | 11 ++++++++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index bcce0277..c5f5fc75 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -13,7 +13,7 @@ on: env: AZURE_WEBAPP_NAME: ${{ secrets.AZURE_WEBAPP_NAME }} AZURE_WEBAPP_PACKAGE_PATH: "./server" - NODE_VERSION: "22.x" # Using current stable LTS for better compatibility + NODE_VERSION: "22.x"1 jobs: build-and-deploy: @@ -26,23 +26,18 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} + node-version: "22.x" cache: "npm" cache-dependency-path: server/package-lock.json - name: Install dependencies run: | cd server - npm ci --omit=dev - - - name: Create .env file placeholder - run: | - cd server - echo "# Env vars managed in Azure Portal" > .env + npm ci --production - name: Deploy to Azure Web App uses: azure/webapps-deploy@v3 with: app-name: ${{ secrets.AZURE_WEBAPP_NAME }} publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} \ No newline at end of file + package: ./server diff --git a/server/src/app.js b/server/src/app.js index 153eb106..f3261ff5 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -12,10 +12,19 @@ dotenv.config(); const app = express(); +app.set("trust proxy", 1); + +// 1. Setup CORS before anything else +// Since you haven't deployed the frontend yet, +// we add localhost so you can still test locally. +const allowedOrigins = [process.env.CLIENT_URL, "http://localhost:5000"].filter( + Boolean +); // Removes undefined values if CLIENT_URL isn't set yet + // Global Middlewares app.use( cors({ - origin: "http://localhost:5000", + origin: allowedOrigins, credentials: true, }) ); From 61165596f882cc16d515f3c48f0250ca87d4aef3 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:00:15 +0200 Subject: [PATCH 07/25] fix(logger): types + global logger declaration and safer overrides --- client/src/global.d.ts | 5 +++++ client/src/lib/logger.ts | 14 ++++++-------- server/src/utils/logger.d.ts | 3 +++ 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 client/src/global.d.ts create mode 100644 server/src/utils/logger.d.ts diff --git a/client/src/global.d.ts b/client/src/global.d.ts new file mode 100644 index 00000000..f46b3e80 --- /dev/null +++ b/client/src/global.d.ts @@ -0,0 +1,5 @@ +declare global { + const logger: typeof import("./lib/logger").logger; +} + +export {}; diff --git a/client/src/lib/logger.ts b/client/src/lib/logger.ts index 181a546c..4a4cb799 100644 --- a/client/src/lib/logger.ts +++ b/client/src/lib/logger.ts @@ -20,7 +20,6 @@ const isProduction = import.meta.env.PROD; // Create logger instance with environment-specific configuration export const logger = createConsola({ level: isDevelopment ? 5 : 3, // 5=trace in dev, 3=info in prod - fancy: isDevelopment, // Colored output in development formatOptions: { colors: isDevelopment, compact: isProduction, @@ -34,22 +33,21 @@ if (isProduction) { logger.level = 2; // 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace // Override logger methods to sanitize sensitive data in production - const originalError = logger.error.bind(logger); - const originalWarn = logger.warn.bind(logger); + const originalError = logger.error.bind(logger) as any; + const originalWarn = logger.warn.bind(logger) as any; - logger.error = (...args: any[]) => { - // In production, only log error messages, not full objects that might contain sensitive data + (logger as any).error = (...args: any[]) => { const sanitized = args.map((arg) => typeof arg === "object" && arg !== null ? "[Object]" : arg ); - originalError(...sanitized); + originalError.apply(originalError, sanitized as any); }; - logger.warn = (...args: any[]) => { + (logger as any).warn = (...args: any[]) => { const sanitized = args.map((arg) => typeof arg === "object" && arg !== null ? "[Object]" : arg ); - originalWarn(...sanitized); + originalWarn.apply(originalWarn, sanitized as any); }; } diff --git a/server/src/utils/logger.d.ts b/server/src/utils/logger.d.ts new file mode 100644 index 00000000..46e7f43d --- /dev/null +++ b/server/src/utils/logger.d.ts @@ -0,0 +1,3 @@ +declare const logger: any; +export { logger }; +export default logger; From 91001d0475e0a18a68bf265d488cce6f17c82a18 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:09:25 +0200 Subject: [PATCH 08/25] feat: add Vercel deployment workflow and update .gitignore for Vercel --- .github/workflows/vercel-deploy.yml | 19 +++++++++++++++++++ client/.gitignore | 1 + 2 files changed, 20 insertions(+) create mode 100644 .github/workflows/vercel-deploy.yml create mode 100644 client/.gitignore diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml new file mode 100644 index 00000000..ae97dbdc --- /dev/null +++ b/.github/workflows/vercel-deploy.yml @@ -0,0 +1,19 @@ +name: Deploy Frontend to Vercel +on: + push: + branches: [main] + paths: ["client/**"] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Deploy to Vercel + uses: amondnet/vercel-action@v20 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + working-directory: ./client + vercel-args: "--prod" diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 00000000..e985853e --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +.vercel From 0299cd58999e0ec0c76f1456d4fd43f504d4cec7 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:12:06 +0200 Subject: [PATCH 09/25] feat: update Vercel deployment workflow to include feat/azure-deploy branch --- .github/workflows/azure-deploy.yml | 6 +++--- .github/workflows/vercel-deploy.yml | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index c5f5fc75..5ceba04d 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -12,8 +12,8 @@ on: env: AZURE_WEBAPP_NAME: ${{ secrets.AZURE_WEBAPP_NAME }} - AZURE_WEBAPP_PACKAGE_PATH: "./server" - NODE_VERSION: "22.x"1 + AZURE_WEBAPP_PACKAGE_PATH: "./server" + NODE_VERSION: "22.x" jobs: build-and-deploy: @@ -40,4 +40,4 @@ jobs: with: app-name: ${{ secrets.AZURE_WEBAPP_NAME }} publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - package: ./server + package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index ae97dbdc..70f7c86a 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -1,7 +1,9 @@ name: Deploy Frontend to Vercel on: push: - branches: [main] + branches: + - main + - feat/azure-deploy paths: ["client/**"] jobs: From 5a040b215dc5763a26cc4e3066cc3b2828c12c50 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:28:07 +0200 Subject: [PATCH 10/25] ci(workflows): allow manual workflow_dispatch for Vercel and Azure --- .github/workflows/vercel-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index 70f7c86a..5382768c 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -5,6 +5,7 @@ on: - main - feat/azure-deploy paths: ["client/**"] + workflow_dispatch: jobs: deploy: From 30967e29e380f6f0af3c6d2951670f05254eed0c Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:33:42 +0200 Subject: [PATCH 11/25] Create vercel.json --- client/vercel.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 client/vercel.json diff --git a/client/vercel.json b/client/vercel.json new file mode 100644 index 00000000..1db5d80e --- /dev/null +++ b/client/vercel.json @@ -0,0 +1,5 @@ +{ + "rewrites": [ + { "source": "/(.*)", "destination": "/" } + ] +} \ No newline at end of file From ae81814b695cfa4a1d645e7a2aacc7b86723fa69 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:34:34 +0200 Subject: [PATCH 12/25] Update vercel.json --- client/vercel.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/vercel.json b/client/vercel.json index 1db5d80e..800c16f5 100644 --- a/client/vercel.json +++ b/client/vercel.json @@ -1,4 +1,6 @@ { + "buildCommand": "npm run build", + "outputDirectory": "dist", "rewrites": [ { "source": "/(.*)", "destination": "/" } ] From a102e7c32b3db17061d15693885c4550bd5f5acb Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:38:16 +0200 Subject: [PATCH 13/25] Update vercel-deploy.yml --- .github/workflows/vercel-deploy.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index 5382768c..5ce1f586 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -1,4 +1,5 @@ name: Deploy Frontend to Vercel + on: push: branches: @@ -10,8 +11,25 @@ on: jobs: deploy: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 + + - name: Use Node.js 18 + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: "npm" + cache-dependency-path: client/package-lock.json + + - name: Install dependencies + working-directory: ./client + run: npm install + + - name: Build frontend + working-directory: ./client + run: npm run build + - name: Deploy to Vercel uses: amondnet/vercel-action@v20 with: @@ -19,4 +37,4 @@ jobs: vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} working-directory: ./client - vercel-args: "--prod" + vercel-args: "--prod" \ No newline at end of file From 5b676b5dfde8345466a8e2107785b71e9c127b3f Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:53:57 +0200 Subject: [PATCH 14/25] Update vercel.json --- client/vercel.json | 1 + 1 file changed, 1 insertion(+) diff --git a/client/vercel.json b/client/vercel.json index 800c16f5..b8ca13da 100644 --- a/client/vercel.json +++ b/client/vercel.json @@ -1,4 +1,5 @@ { + "framework": "vite", "buildCommand": "npm run build", "outputDirectory": "dist", "rewrites": [ From 38f9096fbadff7d7d2d61361c69d33acd9528c5f Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:57:17 +0200 Subject: [PATCH 15/25] Update vercel-deploy.yml --- .github/workflows/vercel-deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index 5ce1f586..a25b9b49 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -19,8 +19,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: 18 - cache: "npm" - cache-dependency-path: client/package-lock.json - name: Install dependencies working-directory: ./client From cb144fd382a3072b3a2c088d8100fb3471958bc0 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:07:34 +0200 Subject: [PATCH 16/25] Update vercel-deploy.yml --- .github/workflows/vercel-deploy.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index a25b9b49..ce71f8fd 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -5,7 +5,7 @@ on: branches: - main - feat/azure-deploy - paths: ["client/**"] + paths: ["client/**", "package.json", "package-lock.json"] # Added root files to trigger workflow_dispatch: jobs: @@ -19,14 +19,20 @@ jobs: uses: actions/setup-node@v4 with: node-version: 18 + cache: 'npm' + # FIX: Point to the actual location of your lock file + cache-dependency-path: 'package-lock.json' - name: Install dependencies - working-directory: ./client + # FIX: Run in root because that's where package.json is run: npm install - name: Build frontend - working-directory: ./client + # If your build script is in the root package.json: run: npm run build + # OR if you must run the build inside the client folder: + # working-directory: ./client + # run: npm run build - name: Deploy to Vercel uses: amondnet/vercel-action@v20 @@ -34,5 +40,5 @@ jobs: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} - working-directory: ./client + working-directory: ./ # Usually root if package.json is there vercel-args: "--prod" \ No newline at end of file From e5e350ddd161240c051d5e65377de7a494d65818 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:08:14 +0200 Subject: [PATCH 17/25] Update vercel.json --- client/vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/vercel.json b/client/vercel.json index b8ca13da..15922359 100644 --- a/client/vercel.json +++ b/client/vercel.json @@ -1,7 +1,7 @@ { "framework": "vite", "buildCommand": "npm run build", - "outputDirectory": "dist", + "outputDirectory": "client/dist", "rewrites": [ { "source": "/(.*)", "destination": "/" } ] From 5ca23bd75a4cb7adc5c0771c51f89be69e623615 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:40:06 +0200 Subject: [PATCH 18/25] Update Vercel configuration for output directory and rewrites --- client/vercel.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/vercel.json b/client/vercel.json index 15922359..af77d6be 100644 --- a/client/vercel.json +++ b/client/vercel.json @@ -1,8 +1,10 @@ { + "version": 2, "framework": "vite", "buildCommand": "npm run build", - "outputDirectory": "client/dist", + "outputDirectory": "dist/public", "rewrites": [ - { "source": "/(.*)", "destination": "/" } + { "source": "/api/(.*)", "destination": "/api/index.js" }, + { "source": "/(.*)", "destination": "/index.html" } ] -} \ No newline at end of file +} From 8e512e792b9de6bb65b96fe74b1b7a05f81fb31e Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:20:38 +0200 Subject: [PATCH 19/25] Add initial package.json with dependencies and scripts --- client/package.json | 152 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 client/package.json diff --git a/client/package.json b/client/package.json new file mode 100644 index 00000000..e0b270f4 --- /dev/null +++ b/client/package.json @@ -0,0 +1,152 @@ +{ + "name": "rest-express", + "version": "1.0.0", + "type": "module", + "license": "MIT", + "scripts": { + "dev": "cross-env NODE_ENV=development tsx server/index.ts", + "build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist", + "start": "NODE_ENV=production node dist/index.js", + "check": "tsc", + "db:push": "drizzle-kit push", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "prepare": "husky" + }, + "dependencies": { + "@emailjs/browser": "^4.4.1", + "@emailjs/nodejs": "^5.0.2", + "@hookform/resolvers": "^3.10.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@neondatabase/serverless": "^0.10.4", + "@radix-ui/react-accordion": "^1.2.4", + "@radix-ui/react-alert-dialog": "^1.1.7", + "@radix-ui/react-aspect-ratio": "^1.1.3", + "@radix-ui/react-avatar": "^1.1.4", + "@radix-ui/react-checkbox": "^1.1.5", + "@radix-ui/react-collapsible": "^1.1.4", + "@radix-ui/react-context-menu": "^2.2.7", + "@radix-ui/react-dialog": "^1.1.7", + "@radix-ui/react-dropdown-menu": "^2.1.7", + "@radix-ui/react-hover-card": "^1.1.7", + "@radix-ui/react-label": "^2.1.3", + "@radix-ui/react-menubar": "^1.1.7", + "@radix-ui/react-navigation-menu": "^1.2.6", + "@radix-ui/react-popover": "^1.1.7", + "@radix-ui/react-progress": "^1.1.3", + "@radix-ui/react-radio-group": "^1.2.4", + "@radix-ui/react-scroll-area": "^1.2.4", + "@radix-ui/react-select": "^2.1.7", + "@radix-ui/react-separator": "^1.1.3", + "@radix-ui/react-slider": "^1.2.4", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-switch": "^1.1.4", + "@radix-ui/react-tabs": "^1.1.4", + "@radix-ui/react-toast": "^1.2.7", + "@radix-ui/react-toggle": "^1.1.3", + "@radix-ui/react-toggle-group": "^1.1.3", + "@radix-ui/react-tooltip": "^1.2.0", + "@react-oauth/google": "^0.12.2", + "@stripe/react-stripe-js": "^5.4.0", + "@stripe/stripe-js": "^8.5.2", + "@tanstack/react-query": "^5.60.5", + "@vercel/blob": "^2.0.0", + "axios": "^1.12.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "connect-pg-simple": "^10.0.0", + "consola": "^3.4.2", + "date-fns": "^3.6.0", + "drizzle-orm": "^0.39.1", + "drizzle-zod": "^0.7.0", + "embla-carousel-react": "^8.6.0", + "express": "^4.21.2", + "express-session": "^1.18.1", + "framer-motion": "^11.18.2", + "googleapis": "^168.0.0", + "ical-generator": "^10.0.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.554.0", + "memorystore": "^1.6.7", + "moment": "^2.30.1", + "next-themes": "^0.4.6", + "nodemailer": "^7.0.9", + "passport": "^0.7.0", + "passport-local": "^1.0.0", + "postcss-import": "^16.1.1", + "postcss-loader": "^8.2.0", + "react": "^18.3.1", + "react-big-calendar": "^1.19.4", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.55.0", + "react-icons": "^5.4.0", + "react-image-crop": "^11.0.10", + "react-resizable-panels": "^2.1.7", + "react-router-dom": "^6.30.1", + "recharts": "^2.15.2", + "stripe": "^19.3.0", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "three": "^0.180.0", + "tw-animate-css": "^1.2.5", + "vaul": "^1.1.2", + "wouter": "^3.3.5", + "ws": "^8.18.0", + "xlsx": "^0.18.5", + "zod": "^3.24.2", + "zod-validation-error": "^3.4.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@replit/vite-plugin-cartographer": "^0.3.1", + "@replit/vite-plugin-dev-banner": "^0.1.1", + "@replit/vite-plugin-runtime-error-modal": "^0.0.3", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "^4.1.3", + "@types/axios": "^0.9.36", + "@types/connect-pg-simple": "^7.0.3", + "@types/express": "4.17.21", + "@types/express-session": "^1.18.0", + "@types/node": "20.16.11", + "@types/passport": "^1.0.16", + "@types/passport-local": "^1.0.38", + "@types/react": "^18.3.11", + "@types/react-big-calendar": "^1.16.3", + "@types/react-dom": "^18.3.1", + "@types/three": "^0.180.0", + "@types/ws": "^8.5.13", + "@typescript-eslint/eslint-plugin": "^8.46.3", + "@typescript-eslint/parser": "^8.46.3", + "autoprefixer": "^10.4.21", + "baseline-browser-mapping": "^2.9.9", + "cross-env": "^10.1.0", + "drizzle-kit": "^0.31.4", + "esbuild": "^0.25.0", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-unused-imports": "^4.3.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.6", + "postcss": "^8.5.6", + "prettier": "^3.6.2", + "tailwindcss": "^3.4.17", + "tsx": "^4.20.5", + "typescript": "^5.9.3", + "vite": "^5.4.20" + }, + "optionalDependencies": { + "bufferutil": "^4.0.8" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ + "prettier --write" + ] + } +} From da098f52035ae7d6396fde0ac3f22c2ad97d6264 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:08:36 +0200 Subject: [PATCH 20/25] Update Vercel deployment configuration and add missing vercel.json --- .github/workflows/vercel-deploy.yml | 35 +++++++++++++++++++++++++---- client/vercel.json | 5 +---- vercel.json | 7 ++++++ 3 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 vercel.json diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index ce71f8fd..541eadad 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -5,7 +5,25 @@ on: branches: - main - feat/azure-deploy - paths: ["client/**", "package.json", "package-lock.json"] # Added root files to trigger + paths: [ + "client/**", + "package.json", + "package-lock.json", + "vercel.json", + "client/vercel.json", + ] # Added Vercel configs to trigger + pull_request: + branches: + - main + - feat/azure-deploy + paths: + [ + "client/**", + "package.json", + "package-lock.json", + "vercel.json", + "client/vercel.json", + ] workflow_dispatch: jobs: @@ -19,9 +37,9 @@ jobs: uses: actions/setup-node@v4 with: node-version: 18 - cache: 'npm' + cache: "npm" # FIX: Point to the actual location of your lock file - cache-dependency-path: 'package-lock.json' + cache-dependency-path: "package-lock.json" - name: Install dependencies # FIX: Run in root because that's where package.json is @@ -34,6 +52,15 @@ jobs: # working-directory: ./client # run: npm run build + - name: Choose Vercel deploy type + id: vercel + run: | + if [ "${{ github.event_name }}" = "push" ]; then + echo "args=--prod" >> $GITHUB_OUTPUT + else + echo "args=" >> $GITHUB_OUTPUT + fi + - name: Deploy to Vercel uses: amondnet/vercel-action@v20 with: @@ -41,4 +68,4 @@ jobs: vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} working-directory: ./ # Usually root if package.json is there - vercel-args: "--prod" \ No newline at end of file + vercel-args: ${{ steps.vercel.outputs.args }} diff --git a/client/vercel.json b/client/vercel.json index af77d6be..02b5eee0 100644 --- a/client/vercel.json +++ b/client/vercel.json @@ -3,8 +3,5 @@ "framework": "vite", "buildCommand": "npm run build", "outputDirectory": "dist/public", - "rewrites": [ - { "source": "/api/(.*)", "destination": "/api/index.js" }, - { "source": "/(.*)", "destination": "/index.html" } - ] + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] } diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000..02b5eee0 --- /dev/null +++ b/vercel.json @@ -0,0 +1,7 @@ +{ + "version": 2, + "framework": "vite", + "buildCommand": "npm run build", + "outputDirectory": "dist/public", + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} From 34aaf0bac2fc893d73b2793697eba1b1d0233031 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:47:43 +0200 Subject: [PATCH 21/25] Refactor API base URL handling across client components - Introduced a new utility function `getApiBaseUrl` in `apiBase.ts` to centralize the retrieval of the API base URL. - Updated all relevant components and pages to use the new `getApiBaseUrl` function instead of directly accessing environment variables. - This change improves maintainability and consistency in how the API base URL is managed throughout the application. --- client/src/components/BazaarList.tsx | 4 +- client/src/components/BigCalendarView.tsx | 4 +- client/src/components/CalendarPopover.tsx | 3 +- client/src/components/CreateEventForm.tsx | 5 +- .../src/components/CreateGymSessionDialog.tsx | 5 +- .../components/CreatePrivilegedUserForm.tsx | 4 +- .../src/components/EditGymSessionDialog.tsx | 5 +- client/src/components/EventCard.tsx | 4 +- client/src/components/EventFeedbackDialog.tsx | 7 +- client/src/components/EventListItem.tsx | 4 +- client/src/components/EventSearch.tsx | 5 +- client/src/components/EventsDetailsDialog.tsx | 5 +- client/src/components/EventsReport.tsx | 4 +- .../components/GoogleCalendarIntegration.tsx | 3 +- client/src/components/GymScheduleViewer.tsx | 8 ++- .../src/components/LoyaltyProgramDialog.tsx | 7 +- .../src/components/NotificationsPopover.tsx | 7 +- client/src/components/Recommendations.tsx | 11 ++- .../src/components/RestrictAccessDialog.tsx | 4 +- .../src/components/WorkshopParticipants.tsx | 4 +- client/src/components/ui/SalesReport.tsx | 4 +- client/src/lib/api.ts | 5 +- client/src/lib/apiBase.ts | 7 ++ client/src/lib/bazaarApi.ts | 5 +- client/src/pages/AdminDashboardPage.tsx | 9 ++- client/src/pages/AdminUsers.tsx | 34 ++++----- .../src/pages/ApprovedLoyaltyPartnersPage.tsx | 5 +- client/src/pages/ArchivedEvents.tsx | 4 +- client/src/pages/AttendeeDetails.tsx | 4 +- client/src/pages/BoothVotePage.tsx | 5 +- client/src/pages/CalendarPage.tsx | 4 +- client/src/pages/CreateBazaar.tsx | 4 +- client/src/pages/CreateConference.tsx | 4 +- client/src/pages/CreateTrip.tsx | 4 +- client/src/pages/CreateTripForm.tsx | 4 +- client/src/pages/CreateWorkshop.tsx | 10 ++- client/src/pages/EditConference.tsx | 4 +- client/src/pages/EditTrip.tsx | 4 +- client/src/pages/EditWorkshop.tsx | 16 ++--- client/src/pages/EmailVerified.tsx | 5 +- client/src/pages/EventsOfficeDashboard.tsx | 9 ++- client/src/pages/EventsOfficePolls.tsx | 4 +- client/src/pages/Home.tsx | 5 +- client/src/pages/LiveMoments.tsx | 4 +- client/src/pages/Login.tsx | 3 +- client/src/pages/MyEvents.tsx | 9 ++- client/src/pages/ProfessorDashboard.tsx | 6 +- client/src/pages/SignUp.tsx | 7 +- client/src/pages/SportsFacilities.tsx | 33 +++++---- client/src/pages/StaffTADashboard.tsx | 11 +-- client/src/pages/StaffUpcomingEvents.tsx | 5 +- client/src/pages/VendorRequests.tsx | 5 +- client/src/pages/WorkshopApprovals.tsx | 4 +- client/src/pages/WorkshopManagement.tsx | 10 ++- server/src/app.js | 71 +++++++++++++++++-- 55 files changed, 262 insertions(+), 168 deletions(-) create mode 100644 client/src/lib/apiBase.ts diff --git a/client/src/components/BazaarList.tsx b/client/src/components/BazaarList.tsx index 8e21b912..3952fa1f 100644 --- a/client/src/components/BazaarList.tsx +++ b/client/src/components/BazaarList.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Search, Store } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; export interface Bazaar { _id: string; @@ -44,6 +45,7 @@ export default function BazaarList({ }: BazaarListProps) { const [searchTerm, setSearchTerm] = useState(""); const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const handleRegisterEvent = async (eventId: string) => { try { @@ -58,7 +60,7 @@ export default function BazaarList({ } const response = await fetch( - `http://localhost:4000/api/events/${eventId}/register`, + `${API_BASE_URL}/api/events/${eventId}/register`, { method: "POST", headers: { diff --git a/client/src/components/BigCalendarView.tsx b/client/src/components/BigCalendarView.tsx index a44354aa..5f0eab7a 100644 --- a/client/src/components/BigCalendarView.tsx +++ b/client/src/components/BigCalendarView.tsx @@ -5,10 +5,10 @@ import "react-big-calendar/lib/css/react-big-calendar.css"; import { Card } from "@/components/ui/card"; import EventDetailsDialog from "@/components/EventsDetailsDialog"; import { Button } from "@/components/ui/button"; +import { getApiBaseUrl } from "@/lib/apiBase"; const localizer = momentLocalizer(moment); -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Event { _id: string; diff --git a/client/src/components/CalendarPopover.tsx b/client/src/components/CalendarPopover.tsx index 67e5055a..a601eeb0 100644 --- a/client/src/components/CalendarPopover.tsx +++ b/client/src/components/CalendarPopover.tsx @@ -8,8 +8,9 @@ import { } from "@/components/ui/popover"; import MiniCalendar from "./MiniCalendar"; import { useLocation } from "wouter"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Event { _id: string; diff --git a/client/src/components/CreateEventForm.tsx b/client/src/components/CreateEventForm.tsx index fcaaea86..2f4527cd 100644 --- a/client/src/components/CreateEventForm.tsx +++ b/client/src/components/CreateEventForm.tsx @@ -15,6 +15,8 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Info, Clock, CalendarIcon } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; export interface CreateEventFormValues { name: string; @@ -104,8 +106,7 @@ export default function CreateEventForm({ (async () => { try { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const res = await fetch(`${baseUrl}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); diff --git a/client/src/components/CreateGymSessionDialog.tsx b/client/src/components/CreateGymSessionDialog.tsx index 6d07892a..f1e75839 100644 --- a/client/src/components/CreateGymSessionDialog.tsx +++ b/client/src/components/CreateGymSessionDialog.tsx @@ -27,6 +27,8 @@ import { } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { cn } from "@/lib/utils"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; type CreateGymSessionDialogProps = { open: boolean; @@ -48,6 +50,7 @@ export default function CreateGymSessionDialog({ onOpenChange, onSuccess, }: CreateGymSessionDialogProps) { + const API_BASE_URL = getApiBaseUrl(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState<{ date: Date | undefined; @@ -96,7 +99,7 @@ export default function CreateGymSessionDialog({ }; const response = await fetch( - "http://localhost:4000/api/facilities/admin/gym/sessions", + `${API_BASE_URL}/api/facilities/admin/gym/sessions`, { method: "POST", headers: { diff --git a/client/src/components/CreatePrivilegedUserForm.tsx b/client/src/components/CreatePrivilegedUserForm.tsx index 2d26070e..12d90cf8 100644 --- a/client/src/components/CreatePrivilegedUserForm.tsx +++ b/client/src/components/CreatePrivilegedUserForm.tsx @@ -10,6 +10,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Props { onSuccess?: () => void; @@ -20,6 +21,7 @@ export default function CreatePrivilegedUserForm({ onSuccess, onCancel, }: Props) { + const API_BASE_URL = getApiBaseUrl(); const [form, setForm] = useState({ firstName: "", lastName: "", @@ -37,7 +39,7 @@ export default function CreatePrivilegedUserForm({ setError(null); try { await axios.post( - "http://localhost:4000/api/users/create-management-account", + `${API_BASE_URL}/api/users/create-management-account`, form, { headers: { diff --git a/client/src/components/EditGymSessionDialog.tsx b/client/src/components/EditGymSessionDialog.tsx index 3ee0d933..577853ca 100644 --- a/client/src/components/EditGymSessionDialog.tsx +++ b/client/src/components/EditGymSessionDialog.tsx @@ -20,6 +20,8 @@ import { import { Calendar } from "@/components/ui/calendar"; import { cn } from "@/lib/utils"; import { CalendarIcon } from "lucide-react"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; type GymSession = { _id: string; @@ -76,6 +78,7 @@ export default function EditGymSessionDialog({ onSuccess, }: EditGymSessionDialogProps) { const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const [isSubmitting, setIsSubmitting] = useState(false); const { @@ -122,7 +125,7 @@ export default function EditGymSessionDialog({ updateData.duration = Number(data.durationMinutes); const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions/${session._id}`, + `${API_BASE_URL}/api/facilities/gym/sessions/${session._id}`, { method: "PATCH", headers: { diff --git a/client/src/components/EventCard.tsx b/client/src/components/EventCard.tsx index fbcfa5a9..dfa758c6 100644 --- a/client/src/components/EventCard.tsx +++ b/client/src/components/EventCard.tsx @@ -35,9 +35,9 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import EventDetailsDialog from "@/components/EventsDetailsDialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); async function deleteEvent(eventId: string) { const token = localStorage.getItem("token"); diff --git a/client/src/components/EventFeedbackDialog.tsx b/client/src/components/EventFeedbackDialog.tsx index 1b72d15f..7b81dfba 100644 --- a/client/src/components/EventFeedbackDialog.tsx +++ b/client/src/components/EventFeedbackDialog.tsx @@ -11,6 +11,7 @@ import { Textarea } from "@/components/ui/textarea"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Star } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface EventFeedbackDialogProps { open: boolean; @@ -63,7 +64,7 @@ export default function EventFeedbackDialog({ setCheckingSubmission(true); const token = localStorage.getItem("token"); const response = await fetch( - `http://localhost:4000/api/feedback/events/${eventId}/me`, + `${getApiBaseUrl()}/api/feedback/events/${eventId}/me`, { headers: { Authorization: `Bearer ${token}`, @@ -97,7 +98,7 @@ export default function EventFeedbackDialog({ setLoading(true); const token = localStorage.getItem("token"); const response = await fetch( - `http://localhost:4000/api/feedback/events/${eventId}`, + `${getApiBaseUrl()}/api/feedback/events/${eventId}`, { headers: { Authorization: `Bearer ${token}`, @@ -193,7 +194,7 @@ export default function EventFeedbackDialog({ if (comment.trim().length > 0) body.comment = comment; const response = await fetch( - `http://localhost:4000/api/feedback/events/${eventId}`, + `${getApiBaseUrl()}/api/feedback/events/${eventId}`, { method: "POST", headers: { diff --git a/client/src/components/EventListItem.tsx b/client/src/components/EventListItem.tsx index 2cb5a921..1307066f 100644 --- a/client/src/components/EventListItem.tsx +++ b/client/src/components/EventListItem.tsx @@ -3,9 +3,9 @@ import { Card, CardContent } from "@/components/ui/card"; import CategoryBadge, { type EventCategory } from "./CategoryBadge"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); async function deleteEvent(eventId: string) { const token = localStorage.getItem("token"); diff --git a/client/src/components/EventSearch.tsx b/client/src/components/EventSearch.tsx index 6b8266f0..1255ddc9 100644 --- a/client/src/components/EventSearch.tsx +++ b/client/src/components/EventSearch.tsx @@ -1,6 +1,8 @@ import { useState, useEffect, useRef, useMemo } from "react"; import { Search, X, Loader2 } from "lucide-react"; import { Input } from "@/components/ui/input"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; export interface EventSearchFilters { type?: string; @@ -34,8 +36,7 @@ export default function EventSearch({ const hasInitiallyFetched = useRef(false); const scrollPositionRef = useRef(0); - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const API_URL = `${API_BASE_URL}/api/events/upcoming`; const SEARCH_URL = `${API_BASE_URL}/api/events/search`; const token = localStorage.getItem("token"); diff --git a/client/src/components/EventsDetailsDialog.tsx b/client/src/components/EventsDetailsDialog.tsx index 68e768b7..64d0e205 100644 --- a/client/src/components/EventsDetailsDialog.tsx +++ b/client/src/components/EventsDetailsDialog.tsx @@ -19,6 +19,8 @@ import { import CategoryBadge from "./CategoryBadge"; import { motion, AnimatePresence } from "framer-motion"; import { useEffect } from "react"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface EventDetailsDialogProps { open: boolean; @@ -46,8 +48,7 @@ export default function EventDetailsDialog({ if (open && event?._id) { const token = localStorage.getItem("token"); if (token) { - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); fetch(`${API_BASE_URL}/api/events/${event._id}/view`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, diff --git a/client/src/components/EventsReport.tsx b/client/src/components/EventsReport.tsx index 781e00ff..136b0f6c 100644 --- a/client/src/components/EventsReport.tsx +++ b/client/src/components/EventsReport.tsx @@ -40,9 +40,9 @@ import { import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface EventStat { _id: string; diff --git a/client/src/components/GoogleCalendarIntegration.tsx b/client/src/components/GoogleCalendarIntegration.tsx index a04a1c86..f15df940 100644 --- a/client/src/components/GoogleCalendarIntegration.tsx +++ b/client/src/components/GoogleCalendarIntegration.tsx @@ -11,8 +11,9 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Card, CardContent } from "@/components/ui/card"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function GoogleCalendarIntegration() { const [isConnected, setIsConnected] = useState(false); diff --git a/client/src/components/GymScheduleViewer.tsx b/client/src/components/GymScheduleViewer.tsx index 7a90dee5..60dab167 100644 --- a/client/src/components/GymScheduleViewer.tsx +++ b/client/src/components/GymScheduleViewer.tsx @@ -22,6 +22,7 @@ import { } from "@/components/ui/alert-dialog"; import { useToast } from "@/hooks/use-toast"; import EditGymSessionDialog from "@/components/EditGymSessionDialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; type GymSession = { _id: string; @@ -78,6 +79,7 @@ export default function GymScheduleViewer({ ); const [isCancelling, setIsCancelling] = useState(false); const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const month = currentDate.getMonth() + 1; // JavaScript months are 0-indexed const year = currentDate.getFullYear(); @@ -98,7 +100,7 @@ export default function GymScheduleViewer({ try { const token = localStorage.getItem("token"); const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions?month=${month}&year=${year}`, + `${API_BASE_URL}/api/facilities/gym/sessions?month=${month}&year=${year}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, } @@ -143,7 +145,7 @@ export default function GymScheduleViewer({ const handleRegister = async (sessionId: string) => { //gym session registration logic const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions/${sessionId}/register`, + `${API_BASE_URL}/api/facilities/gym/sessions/${sessionId}/register`, { method: "POST", headers: { @@ -196,7 +198,7 @@ export default function GymScheduleViewer({ } const response = await fetch( - `http://localhost:4000/api/facilities/gym/sessions/${selectedSession._id}/cancel`, + `${API_BASE_URL}/api/facilities/gym/sessions/${selectedSession._id}/cancel`, { method: "PATCH", headers: { diff --git a/client/src/components/LoyaltyProgramDialog.tsx b/client/src/components/LoyaltyProgramDialog.tsx index 471388dc..46156c03 100644 --- a/client/src/components/LoyaltyProgramDialog.tsx +++ b/client/src/components/LoyaltyProgramDialog.tsx @@ -21,6 +21,7 @@ import { } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface LoyaltyProgramDialogProps { open: boolean; @@ -67,7 +68,7 @@ export default function LoyaltyProgramDialog({ } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/status", + `${getApiBaseUrl()}/api/loyalty-partners/status`, { method: "GET", headers: { @@ -107,7 +108,7 @@ export default function LoyaltyProgramDialog({ } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/cancel", + `${getApiBaseUrl()}/api/loyalty-partners/cancel`, { method: "DELETE", headers: { @@ -166,7 +167,7 @@ export default function LoyaltyProgramDialog({ } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/apply", + `${getApiBaseUrl()}/api/loyalty-partners/apply`, { method: "POST", headers: { diff --git a/client/src/components/NotificationsPopover.tsx b/client/src/components/NotificationsPopover.tsx index a1f5c3b9..a992a1e2 100644 --- a/client/src/components/NotificationsPopover.tsx +++ b/client/src/components/NotificationsPopover.tsx @@ -18,6 +18,7 @@ import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useToast } from "@/hooks/use-toast"; import { formatDistanceToNow, format } from "date-fns"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Notification { _id: string; @@ -69,7 +70,7 @@ export default function NotificationsPopover() { const token = localStorage.getItem("token"); if (!token) return; - const res = await fetch("http://localhost:4000/api/notifications/me", { + const res = await fetch(`${getApiBaseUrl()}/api/notifications/me`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -96,7 +97,7 @@ export default function NotificationsPopover() { if (!token) return; const res = await fetch( - `http://localhost:4000/api/notifications/${notificationId}`, + `${getApiBaseUrl()}/api/notifications/${notificationId}`, { method: "PATCH", headers: { @@ -132,7 +133,7 @@ export default function NotificationsPopover() { if (!token) return; const res = await fetch( - `http://localhost:4000/api/notifications/${notificationToDelete}`, + `${getApiBaseUrl()}/api/notifications/${notificationToDelete}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}` }, diff --git a/client/src/components/Recommendations.tsx b/client/src/components/Recommendations.tsx index 7b0becbf..ff7cc001 100644 --- a/client/src/components/Recommendations.tsx +++ b/client/src/components/Recommendations.tsx @@ -8,6 +8,8 @@ import { Button } from "@/components/ui/button"; import EventDetailsDialog from "@/components/EventsDetailsDialog"; import { useToast } from "@/hooks/use-toast"; import { EventPaymentDialog } from "./EventPaymentDialog"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface RecommendationResponse { type: "popular" | "personalized"; @@ -90,8 +92,7 @@ function CompactEventCard({ event }: { event: any }) { } setIsRegistering(true); try { - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const res = await fetch( `${API_BASE_URL}/api/events/${event._id}/register`, { @@ -286,8 +287,7 @@ export default function Recommendations() { // const handleReset = async () => { // try { // const token = localStorage.getItem("token"); - // const API_BASE_URL = - // import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + // const API_BASE_URL = getApiBaseUrl(); // // await fetch(`${API_BASE_URL}/api/recommendations/reset`, { // method: "POST", @@ -313,8 +313,7 @@ export default function Recommendations() { return; } - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const res = await fetch(`${API_BASE_URL}/api/recommendations`, { headers: { Authorization: `Bearer ${token}` }, }); diff --git a/client/src/components/RestrictAccessDialog.tsx b/client/src/components/RestrictAccessDialog.tsx index d5029340..49970711 100644 --- a/client/src/components/RestrictAccessDialog.tsx +++ b/client/src/components/RestrictAccessDialog.tsx @@ -11,9 +11,9 @@ import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface RestrictAccessDialogProps { open: boolean; diff --git a/client/src/components/WorkshopParticipants.tsx b/client/src/components/WorkshopParticipants.tsx index 8bbfd558..0c522268 100644 --- a/client/src/components/WorkshopParticipants.tsx +++ b/client/src/components/WorkshopParticipants.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Users } from "lucide-react"; import { Badge } from "@/components/ui/badge"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Participant { _id: string; @@ -24,8 +25,7 @@ const WorkshopParticipants: React.FC = ({ workshopId }) => { setLoading(true); try { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const res = await axios.get( `${baseUrl}/api/events/workshops/${workshopId}/participants`, { diff --git a/client/src/components/ui/SalesReport.tsx b/client/src/components/ui/SalesReport.tsx index f21ff867..91409416 100644 --- a/client/src/components/ui/SalesReport.tsx +++ b/client/src/components/ui/SalesReport.tsx @@ -25,9 +25,9 @@ import { import { Calendar } from "@/components/ui/calendar"; import { Loader2, Download, CalendarIcon } from "lucide-react"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface EventSale { _id: string; diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 98ba6973..c505bb44 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -1,7 +1,8 @@ import axios from "axios"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +import { getApiBaseUrl } from "./apiBase"; + +const API_BASE_URL = getApiBaseUrl(); export const api = axios.create({ baseURL: `${API_BASE_URL}/api`, diff --git a/client/src/lib/apiBase.ts b/client/src/lib/apiBase.ts new file mode 100644 index 00000000..b9d1a87d --- /dev/null +++ b/client/src/lib/apiBase.ts @@ -0,0 +1,7 @@ +export function getApiBaseUrl(): string { + return ( + import.meta.env.VITE_API_BASE_URL || + (import.meta as any).env?.VITE_API_URL || + "http://localhost:4000" + ); +} diff --git a/client/src/lib/bazaarApi.ts b/client/src/lib/bazaarApi.ts index 7e2d44b1..1715fc58 100644 --- a/client/src/lib/bazaarApi.ts +++ b/client/src/lib/bazaarApi.ts @@ -1,6 +1,7 @@ +import { getApiBaseUrl } from "./apiBase"; + // API service for bazaars -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export interface Bazaar { _id: string; diff --git a/client/src/pages/AdminDashboardPage.tsx b/client/src/pages/AdminDashboardPage.tsx index 764c6635..e25e643e 100644 --- a/client/src/pages/AdminDashboardPage.tsx +++ b/client/src/pages/AdminDashboardPage.tsx @@ -13,9 +13,10 @@ import EventSort from "@/components/EventSort"; import EventFilters, { EventFilterState } from "@/components/EventFilters"; import EventCountBadge from "@/components/EventCountBadge"; import EventsDetailsDialog from "@/components/EventsDetailsDialog"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Conference { _id: string; @@ -226,12 +227,10 @@ export default function AdminDashboardPage() { }, []); useEffect(() => { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; const token = localStorage.getItem("token"); const fetchProfessors = async () => { try { - const res = await fetch(`${baseUrl}/api/users/professors`, { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), "Content-Type": "application/json", diff --git a/client/src/pages/AdminUsers.tsx b/client/src/pages/AdminUsers.tsx index ed07bb86..f012f769 100644 --- a/client/src/pages/AdminUsers.tsx +++ b/client/src/pages/AdminUsers.tsx @@ -15,6 +15,7 @@ import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import CreatePrivilegedUserForm from "@/components/CreatePrivilegedUserForm"; +import { getApiBaseUrl } from "@/lib/apiBase"; import { Table, @@ -59,6 +60,7 @@ interface User { export default function AdminUsers() { const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const [users, setUsers] = useState([]); const [pendingUsers, setPendingUsers] = useState([]); const [loading, setLoading] = useState(true); @@ -103,7 +105,7 @@ export default function AdminUsers() { ) => { try { await axios.patch( - `http://localhost:4000/api/users/${userId}/block-status`, + `${API_BASE_URL}/api/users/${userId}/block-status`, { action }, { headers: { @@ -132,7 +134,7 @@ export default function AdminUsers() { const handleDeleteUser = async (userId: string) => { try { - await axios.delete(`http://localhost:4000/api/users/${userId}/delete`, { + await axios.delete(`${API_BASE_URL}/api/users/${userId}/delete`, { headers: { Authorization: `Bearer ${localStorage.getItem("token")}`, }, @@ -177,14 +179,11 @@ export default function AdminUsers() { try { setLoading(true); setError(null); - const response = await axios.get( - "http://localhost:4000/api/users/getusers", - { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - } - ); + const response = await axios.get(`${API_BASE_URL}/api/users/getusers`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); setUsers(response.data.data); } catch (err) { logger.error("Failed to fetch users:", err); @@ -198,14 +197,11 @@ export default function AdminUsers() { const fetchPendingUsers = async () => { try { setPendingLoading(true); - const response = await axios.get( - "http://localhost:4000/api/users/pending", - { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - } - ); + const response = await axios.get(`${API_BASE_URL}/api/users/pending`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); setPendingUsers(response.data.data); } catch (err) { logger.error("Failed to fetch pending users:", err); @@ -219,7 +215,7 @@ export default function AdminUsers() { try { setAssigningRole(userId); await axios.patch( - `http://localhost:4000/api/users/${userId}/assign-role`, + `${API_BASE_URL}/api/users/${userId}/assign-role`, { role }, { headers: { diff --git a/client/src/pages/ApprovedLoyaltyPartnersPage.tsx b/client/src/pages/ApprovedLoyaltyPartnersPage.tsx index b15512a7..a7819a11 100644 --- a/client/src/pages/ApprovedLoyaltyPartnersPage.tsx +++ b/client/src/pages/ApprovedLoyaltyPartnersPage.tsx @@ -24,6 +24,8 @@ import StaffHeader from "@/components/StaffHeader"; import ProfessorHeader from "@/components/ProfessorHeader"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import AdminHeader from "@/components/AdminHeader"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Partner { vendorId: string; @@ -39,6 +41,7 @@ interface Partner { export default function ApprovedLoyaltyPartnersPage() { const [, setLocation] = useLocation(); const { toast } = useToast(); + const API_BASE_URL = getApiBaseUrl(); const [partners, setPartners] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -86,7 +89,7 @@ export default function ApprovedLoyaltyPartnersPage() { } const response = await fetch( - "http://localhost:4000/api/loyalty-partners/partners", + `${API_BASE_URL}/api/loyalty-partners/partners`, { method: "GET", headers: { diff --git a/client/src/pages/ArchivedEvents.tsx b/client/src/pages/ArchivedEvents.tsx index 3b070a94..845d2607 100644 --- a/client/src/pages/ArchivedEvents.tsx +++ b/client/src/pages/ArchivedEvents.tsx @@ -7,9 +7,9 @@ import EventDetailsDialog from "@/components/EventsDetailsDialog"; import { useToast } from "@/hooks/use-toast"; import EmptyState from "@/components/EmptyState"; import { Archive } from "lucide-react"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function ArchivedEvents() { const [events, setEvents] = useState([]); diff --git a/client/src/pages/AttendeeDetails.tsx b/client/src/pages/AttendeeDetails.tsx index 71eb17c1..bec00678 100644 --- a/client/src/pages/AttendeeDetails.tsx +++ b/client/src/pages/AttendeeDetails.tsx @@ -24,6 +24,7 @@ import { import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import Logo from "@/components/Logo"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface AttendeeData { attendee: { @@ -54,8 +55,7 @@ export default function AttendeeDetails() { const [data, setData] = useState(null); const [errorMessage, setErrorMessage] = useState(""); - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); useEffect(() => { const fetchAttendeeData = async () => { diff --git a/client/src/pages/BoothVotePage.tsx b/client/src/pages/BoothVotePage.tsx index 6922d18b..a7dcee5a 100644 --- a/client/src/pages/BoothVotePage.tsx +++ b/client/src/pages/BoothVotePage.tsx @@ -10,9 +10,10 @@ import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { useToast } from "@/hooks/use-toast"; import { CheckCircle2 } from "lucide-react"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface PollOption { _id: string; diff --git a/client/src/pages/CalendarPage.tsx b/client/src/pages/CalendarPage.tsx index 1e222482..76506956 100644 --- a/client/src/pages/CalendarPage.tsx +++ b/client/src/pages/CalendarPage.tsx @@ -14,7 +14,9 @@ import { SelectValue, } from "@/components/ui/select"; -const API_BASE_URL = "http://localhost:4000"; +import { getApiBaseUrl } from "@/lib/apiBase"; + +const API_BASE_URL = getApiBaseUrl(); interface Event { _id: string; diff --git a/client/src/pages/CreateBazaar.tsx b/client/src/pages/CreateBazaar.tsx index 82585588..ab0f3344 100644 --- a/client/src/pages/CreateBazaar.tsx +++ b/client/src/pages/CreateBazaar.tsx @@ -25,6 +25,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; @@ -56,8 +57,7 @@ export default function CreateBazaar() { { value: "vendor", label: "Vendors" }, ]; - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; + const API_BASE_URL = getApiBaseUrl(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); diff --git a/client/src/pages/CreateConference.tsx b/client/src/pages/CreateConference.tsx index dc021ad7..74678df1 100644 --- a/client/src/pages/CreateConference.tsx +++ b/client/src/pages/CreateConference.tsx @@ -6,9 +6,9 @@ import CreateEventForm, { } from "@/components/CreateEventForm"; import { useLocation } from "wouter"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function CreateConference() { const [, setLocation] = useLocation(); diff --git a/client/src/pages/CreateTrip.tsx b/client/src/pages/CreateTrip.tsx index abd90557..0ad4b881 100644 --- a/client/src/pages/CreateTrip.tsx +++ b/client/src/pages/CreateTrip.tsx @@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import { useLocation } from "wouter"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function TripManagement() { const [, setLocation] = useLocation(); @@ -15,8 +16,7 @@ export default function TripManagement() { const [showDescriptionModal, setShowDescriptionModal] = useState(false); const [selectedTrip, setSelectedTrip] = useState(null); - const apiBase = - (import.meta.env.VITE_API_URL as string) || "http://localhost:4000"; + const apiBase = getApiBaseUrl(); // Fetch trips from the backend admin trips endpoint and handle ApiResponse wrapper const fetchTrips = async () => { diff --git a/client/src/pages/CreateTripForm.tsx b/client/src/pages/CreateTripForm.tsx index 000cb21b..2364c617 100644 --- a/client/src/pages/CreateTripForm.tsx +++ b/client/src/pages/CreateTripForm.tsx @@ -18,6 +18,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import { useLocation } from "wouter"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function CreateTripForm() { const { toast } = useToast(); @@ -44,8 +45,7 @@ export default function CreateTripForm() { { value: "professor", label: "Professors" }, ]; - const apiBase = - (import.meta.env.VITE_API_URL as string) || "http://localhost:4000"; + const apiBase = getApiBaseUrl(); const handleSubmit = async (e?: FormEvent) => { if (e) { diff --git a/client/src/pages/CreateWorkshop.tsx b/client/src/pages/CreateWorkshop.tsx index 5cf97bab..e11c7b34 100644 --- a/client/src/pages/CreateWorkshop.tsx +++ b/client/src/pages/CreateWorkshop.tsx @@ -24,6 +24,7 @@ import { Calendar as CalendarComponent } from "@/components/ui/calendar"; import { Checkbox } from "@/components/ui/checkbox"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface WorkshopFormData { name: string; @@ -45,6 +46,7 @@ interface WorkshopFormData { export default function CreateWorkshop() { const [, setLocation] = useLocation(); + const apiBaseUrl = getApiBaseUrl(); const [formData, setFormData] = useState({ name: "", @@ -108,9 +110,7 @@ export default function CreateWorkshop() { setLoading(false); return; } - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; - const res = await fetch(`${baseUrl}/api/users/professors`, { + const res = await fetch(`${apiBaseUrl}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { @@ -181,9 +181,7 @@ export default function CreateWorkshop() { return; } - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; - const res = await fetch(`${baseUrl}/api/events/workshops`, { + const res = await fetch(`${apiBaseUrl}/api/events/workshops`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/client/src/pages/EditConference.tsx b/client/src/pages/EditConference.tsx index d6d9a70f..ee93a42a 100644 --- a/client/src/pages/EditConference.tsx +++ b/client/src/pages/EditConference.tsx @@ -17,9 +17,9 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Conference { _id: string; diff --git a/client/src/pages/EditTrip.tsx b/client/src/pages/EditTrip.tsx index 99bdae80..0bfe4370 100644 --- a/client/src/pages/EditTrip.tsx +++ b/client/src/pages/EditTrip.tsx @@ -27,9 +27,9 @@ import { } from "@/components/ui/alert-dialog"; import EventsOfficeHeader from "@/components/EventsOfficeHeader"; import { cn } from "@/lib/utils"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); export default function EditTrip() { const [, params] = useRoute("/events-office/events/trip/edit/:id"); diff --git a/client/src/pages/EditWorkshop.tsx b/client/src/pages/EditWorkshop.tsx index 36229f2d..cef1012e 100644 --- a/client/src/pages/EditWorkshop.tsx +++ b/client/src/pages/EditWorkshop.tsx @@ -31,6 +31,8 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface WorkshopFormData { name: string; @@ -54,6 +56,7 @@ export default function EditWorkshop() { const [, setLocation] = useLocation(); const { toast } = useToast(); const workshopId = params?.id; + const API_BASE_URL = getApiBaseUrl(); const [formData, setFormData] = useState({ name: "", @@ -95,7 +98,7 @@ export default function EditWorkshop() { if (!token) return; try { - const res = await fetch("http://localhost:4000/api/users/professors", { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { @@ -124,12 +127,9 @@ export default function EditWorkshop() { return; } - const res = await fetch( - `http://localhost:4000/api/events/${workshopId}`, - { - headers: { Authorization: `Bearer ${token}` }, - } - ); + const res = await fetch(`${API_BASE_URL}/api/events/${workshopId}`, { + headers: { Authorization: `Bearer ${token}` }, + }); if (!res.ok) { throw new Error("Failed to fetch workshop details"); @@ -222,7 +222,7 @@ export default function EditWorkshop() { if (!token) throw new Error("Not authenticated. Please log in first."); const res = await fetch( - `http://localhost:4000/api/events/workshops/${workshopId}`, + `${API_BASE_URL}/api/events/workshops/${workshopId}`, { method: "PATCH", headers: { diff --git a/client/src/pages/EmailVerified.tsx b/client/src/pages/EmailVerified.tsx index 35e1deb6..20e68797 100644 --- a/client/src/pages/EmailVerified.tsx +++ b/client/src/pages/EmailVerified.tsx @@ -8,10 +8,13 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function EmailVerified() { const [, params] = useRoute("/verify-email/:token"); const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [status, setStatus] = useState<"loading" | "success" | "error">( "loading" ); @@ -33,7 +36,7 @@ export default function EmailVerified() { `/api/auth/verify-email/${token}` ); const response = await fetch( - `http://localhost:4000/api/auth/verify-email/${token}` + `${API_BASE_URL}/api/auth/verify-email/${token}` ); const data = await response.json(); logger.info("Verification response:", response.status, data); diff --git a/client/src/pages/EventsOfficeDashboard.tsx b/client/src/pages/EventsOfficeDashboard.tsx index 4626a0f6..95d06786 100644 --- a/client/src/pages/EventsOfficeDashboard.tsx +++ b/client/src/pages/EventsOfficeDashboard.tsx @@ -46,9 +46,10 @@ import CreateGymSessionDialog from "@/components/CreateGymSessionDialog"; import { getEventImage } from "@/lib/eventImages"; import { useToast } from "@/hooks/use-toast"; import RestrictAccessDialog from "@/components/RestrictAccessDialog"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface Bazaar { _id: string; @@ -725,11 +726,9 @@ export default function EventsOfficeDashboard() { // Fetch professors for dropdown useEffect(() => { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_BASE_URL || "http://localhost:4000"; const fetchProfessors = async () => { try { - const res = await fetch(`${baseUrl}/api/users/professors`, { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), "Content-Type": "application/json", diff --git a/client/src/pages/EventsOfficePolls.tsx b/client/src/pages/EventsOfficePolls.tsx index 1b5c3c49..a694e385 100644 --- a/client/src/pages/EventsOfficePolls.tsx +++ b/client/src/pages/EventsOfficePolls.tsx @@ -22,9 +22,9 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Checkbox } from "@/components/ui/checkbox"; +import { getApiBaseUrl } from "@/lib/apiBase"; -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); interface PollOption { _id: string; diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index ba057dad..bcb83836 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -10,6 +10,8 @@ import EmptyState from "@/components/EmptyState"; import Recommendations from "@/components/Recommendations"; import { useToast } from "@/hooks/use-toast"; import { getEventImage } from "@/lib/eventImages"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; // Define Event type for type safety interface Event { @@ -228,8 +230,7 @@ export default function Home() { ]); useEffect(() => { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const token = localStorage.getItem("token"); const fetchProfessors = async () => { try { diff --git a/client/src/pages/LiveMoments.tsx b/client/src/pages/LiveMoments.tsx index 8b657271..cbcd14bd 100644 --- a/client/src/pages/LiveMoments.tsx +++ b/client/src/pages/LiveMoments.tsx @@ -24,6 +24,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Event { _id: string; @@ -50,8 +51,7 @@ interface EventImage { uploadedAt: string; } -const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:4000"; +const API_BASE_URL = getApiBaseUrl(); // Component for displaying a single event with sliding images function LiveEventCard({ event }: { event: Event }) { diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx index 4f1bff2c..db13447a 100644 --- a/client/src/pages/Login.tsx +++ b/client/src/pages/Login.tsx @@ -16,6 +16,7 @@ import { import Logo from "@/components/Logo"; import { useToast } from "@/hooks/use-toast"; import { ToastProvider, ToastViewport } from "@/components/ui/toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function Login() { const [location] = useLocation(); @@ -62,7 +63,7 @@ export default function Login() { e.preventDefault(); try { - const res = await fetch("http://localhost:4000/api/auth/login", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), diff --git a/client/src/pages/MyEvents.tsx b/client/src/pages/MyEvents.tsx index b66b21bf..08c256a5 100644 --- a/client/src/pages/MyEvents.tsx +++ b/client/src/pages/MyEvents.tsx @@ -9,6 +9,7 @@ import ProfessorHeader from "@/components/ProfessorHeader"; import StaffHeader from "@/components/StaffHeader"; import EventCard from "@/components/EventCard"; import EventFeedbackDialog from "@/components/EventFeedbackDialog"; +import { getApiBaseUrl } from "@/lib/apiBase"; // Helper to get token const getToken = () => localStorage.getItem("token"); @@ -32,6 +33,7 @@ interface RegisteredEvent { export default function MyEvents() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [registeredEvents, setRegisteredEvents] = useState( [] ); @@ -54,9 +56,7 @@ export default function MyEvents() { const fetchEvents = async () => { setLoading(true); try { - const apiBaseUrl = "http://localhost:4000"; - - const res = await fetch(`${apiBaseUrl}/api/events/me/events`, { + const res = await fetch(`${API_BASE_URL}/api/events/me/events`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}`, @@ -130,8 +130,7 @@ export default function MyEvents() { setDetailsLoading(true); setDialogOpen(true); try { - const apiBaseUrl = "http://localhost:4000"; - const res = await fetch(`${apiBaseUrl}/api/events/${eventId}`, { + const res = await fetch(`${API_BASE_URL}/api/events/${eventId}`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}`, diff --git a/client/src/pages/ProfessorDashboard.tsx b/client/src/pages/ProfessorDashboard.tsx index ff29edfb..d39e025a 100644 --- a/client/src/pages/ProfessorDashboard.tsx +++ b/client/src/pages/ProfessorDashboard.tsx @@ -16,6 +16,7 @@ import EventCard from "@/components/EventCard"; import EmptyState from "@/components/EmptyState"; import { useToast } from "@/hooks/use-toast"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Workshop { _id: string; @@ -26,6 +27,7 @@ interface Workshop { export default function ProfessorDashboard() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [workshops, setWorkshops] = useState([]); const [loading, setLoading] = useState(true); const [userName, setUserName] = useState(""); @@ -90,7 +92,7 @@ export default function ProfessorDashboard() { return; } - const res = await fetch("http://localhost:4000/api/events/me/workshops", { + const res = await fetch(`${API_BASE_URL}/api/events/me/workshops`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -109,7 +111,7 @@ export default function ProfessorDashboard() { try { const token = localStorage.getItem("token"); if (!token) return; - const res = await fetch("http://localhost:4000/api/users/professors", { + const res = await fetch(`${API_BASE_URL}/api/users/professors`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) return; diff --git a/client/src/pages/SignUp.tsx b/client/src/pages/SignUp.tsx index 05398685..eca725ba 100644 --- a/client/src/pages/SignUp.tsx +++ b/client/src/pages/SignUp.tsx @@ -17,6 +17,7 @@ import Logo from "@/components/Logo"; import { bazaarApiService } from "@/lib/bazaarApi"; import { useToast } from "@/hooks/use-toast"; import { FileUploadWithCrop } from "@/components/ui/file-upload-with-crop"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function SignUp() { const [, setLocation] = useLocation(); @@ -97,7 +98,7 @@ export default function SignUp() { } try { - const res = await fetch("http://localhost:4000/api/auth/signup", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -148,7 +149,7 @@ export default function SignUp() { } try { - const res = await fetch("http://localhost:4000/api/auth/signup", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(staffForm), @@ -177,7 +178,7 @@ export default function SignUp() { e.preventDefault(); try { - const res = await fetch("http://localhost:4000/api/auth/signup", { + const res = await fetch(`${getApiBaseUrl()}/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/client/src/pages/SportsFacilities.tsx b/client/src/pages/SportsFacilities.tsx index b61910af..f14a8bc0 100644 --- a/client/src/pages/SportsFacilities.tsx +++ b/client/src/pages/SportsFacilities.tsx @@ -14,6 +14,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import GymScheduleViewer from "@/components/GymScheduleViewer"; import CreateGymSessionDialog from "@/components/CreateGymSessionDialog"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; type Slot = { startTime: string; @@ -51,6 +52,7 @@ const formatTimeToAMPM = (time: string) => { export default function SportsFacilities() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [courtSchedules, setCourtSchedules] = useState< Record >({ @@ -91,7 +93,7 @@ export default function SportsFacilities() { const token = localStorage.getItem("token"); try { setLoading(true); - const res = await fetch("http://localhost:4000/api/facilities/courts", { + const res = await fetch(`${API_BASE_URL}/api/facilities/courts`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); @@ -218,22 +220,19 @@ export default function SportsFacilities() { try { const token = localStorage.getItem("token"); - const res = await fetch( - "http://localhost:4000/api/facilities/courts/reserve", - { - method: "POST", - headers: { - "Content-Type": "application/json", - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }, - body: JSON.stringify({ - date: bodyDate, - courtType, - startTime, - endTime, - }), - } - ); + const res = await fetch(`${API_BASE_URL}/api/facilities/courts/reserve`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + date: bodyDate, + courtType, + startTime, + endTime, + }), + }); const data = await res.json(); diff --git a/client/src/pages/StaffTADashboard.tsx b/client/src/pages/StaffTADashboard.tsx index 9099d719..8d2604e1 100644 --- a/client/src/pages/StaffTADashboard.tsx +++ b/client/src/pages/StaffTADashboard.tsx @@ -25,6 +25,7 @@ import EventSort from "@/components/EventSort"; import EmptyState from "@/components/EmptyState"; import { useToast } from "@/hooks/use-toast"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface RegisteredEvent { _id: string; @@ -47,6 +48,7 @@ interface MyEvent { export default function StaffTADashboard() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [events, setEvents] = useState([]); const [registeredEvents, setRegisteredEvents] = useState([]); const [loading, setLoading] = useState(true); @@ -100,7 +102,7 @@ export default function StaffTADashboard() { return; } - const res = await fetch("http://localhost:4000/api/events/me/events", { + const res = await fetch(`${API_BASE_URL}/api/events/me/events`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -120,7 +122,7 @@ export default function StaffTADashboard() { const token = localStorage.getItem("token"); if (!token) return; - const res = await fetch("http://localhost:4000/api/events/me/events", { + const res = await fetch(`${API_BASE_URL}/api/events/me/events`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, @@ -156,7 +158,7 @@ export default function StaffTADashboard() { setDialogOpen(true); try { const token = localStorage.getItem("token"); - const res = await fetch(`http://localhost:4000/api/events/${eventId}`, { + const res = await fetch(`${API_BASE_URL}/api/events/${eventId}`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, @@ -190,8 +192,7 @@ export default function StaffTADashboard() { // Fetch professors for filter useEffect(() => { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_BASE_URL || "http://localhost:4000"; + const baseUrl = API_BASE_URL; const fetchProfessors = async () => { try { diff --git a/client/src/pages/StaffUpcomingEvents.tsx b/client/src/pages/StaffUpcomingEvents.tsx index b9156a80..c7a32b3b 100644 --- a/client/src/pages/StaffUpcomingEvents.tsx +++ b/client/src/pages/StaffUpcomingEvents.tsx @@ -6,6 +6,8 @@ import EventSearch, { EventSearchFilters } from "@/components/EventSearch"; import EventSort from "@/components/EventSort"; import EmptyState from "@/components/EmptyState"; import { useToast } from "@/hooks/use-toast"; +import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Event { _id: string; @@ -202,8 +204,7 @@ export default function StaffUpcomingEvents() { ]); useEffect(() => { - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; + const baseUrl = getApiBaseUrl(); const token = localStorage.getItem("token"); const fetchProfessors = async () => { try { diff --git a/client/src/pages/VendorRequests.tsx b/client/src/pages/VendorRequests.tsx index f73aa23e..06ced981 100644 --- a/client/src/pages/VendorRequests.tsx +++ b/client/src/pages/VendorRequests.tsx @@ -30,6 +30,7 @@ import { Label } from "@/components/ui/label"; import { useState, useEffect } from "react"; import { logger } from "@/lib/logger"; import { useToast } from "@/hooks/use-toast"; +import { getApiBaseUrl } from "@/lib/apiBase"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Table, @@ -68,7 +69,7 @@ async function updateVendorStatus( try { const response = await fetch( - `http://localhost:4000/api/applications/${requestId}/status`, + `${getApiBaseUrl()}/api/applications/${requestId}/status`, { method: "PATCH", headers: { @@ -195,7 +196,7 @@ export default function VendorRequests() { try { setLoading(true); setError(null); - const res = await fetch(`http://localhost:4000/api/applications`, { + const res = await fetch(`${getApiBaseUrl()}/api/applications`, { method: "GET", headers: { "Content-Type": "application/json", diff --git a/client/src/pages/WorkshopApprovals.tsx b/client/src/pages/WorkshopApprovals.tsx index b9e092ee..c34ee2e3 100644 --- a/client/src/pages/WorkshopApprovals.tsx +++ b/client/src/pages/WorkshopApprovals.tsx @@ -45,6 +45,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getApiBaseUrl } from "@/lib/apiBase"; export default function WorkshopApprovals() { const [allWorkshops, setAllWorkshops] = useState([]); @@ -59,8 +60,7 @@ export default function WorkshopApprovals() { const [searchInput, setSearchInput] = useState(""); const searchTimerRef = useRef(null); - const apiBase = - (import.meta.env.VITE_API_URL as string) || "http://localhost:4000"; + const apiBase = getApiBaseUrl(); // Compute unique faculties dynamically from workshops data const facultyOptions = useMemo(() => { diff --git a/client/src/pages/WorkshopManagement.tsx b/client/src/pages/WorkshopManagement.tsx index 5c5781a6..637d4df3 100644 --- a/client/src/pages/WorkshopManagement.tsx +++ b/client/src/pages/WorkshopManagement.tsx @@ -13,6 +13,7 @@ import { } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { logger } from "@/lib/logger"; +import { getApiBaseUrl } from "@/lib/apiBase"; interface Workshop { _id: string; @@ -32,6 +33,7 @@ interface Workshop { export default function WorkshopManagement() { const [, setLocation] = useLocation(); + const API_BASE_URL = getApiBaseUrl(); const [workshops, setWorkshops] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); @@ -49,10 +51,8 @@ export default function WorkshopManagement() { setParticipantsError(""); try { const token = localStorage.getItem("token"); - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; const res = await fetch( - `${baseUrl}/api/events/workshops/${workshopId}/participants`, + `${API_BASE_URL}/api/events/workshops/${workshopId}/participants`, { headers: token ? { Authorization: `Bearer ${token}` } : undefined, } @@ -89,9 +89,7 @@ export default function WorkshopManagement() { return; } - const baseUrl = - (import.meta as any).env.VITE_API_URL || "http://localhost:4000"; - const res = await fetch(`${baseUrl}/api/events/me/workshops`, { + const res = await fetch(`${API_BASE_URL}/api/events/me/workshops`, { headers: { Authorization: `Bearer ${token}` }, }); diff --git a/server/src/app.js b/server/src/app.js index f3261ff5..c91f9802 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -17,14 +17,71 @@ app.set("trust proxy", 1); // 1. Setup CORS before anything else // Since you haven't deployed the frontend yet, // we add localhost so you can still test locally. -const allowedOrigins = [process.env.CLIENT_URL, "http://localhost:5000"].filter( - Boolean -); // Removes undefined values if CLIENT_URL isn't set yet +const isProd = process.env.NODE_ENV === "production"; + +const envAllowed = [process.env.CLIENT_URL, process.env.CLIENT_ORIGIN] + .filter(Boolean) + .flatMap((v) => String(v).split(",")) + .map((v) => v.trim()) + .filter(Boolean); + +const allowVercelPreviews = + String(process.env.ALLOW_VERCEL_PREVIEWS || "").toLowerCase() === "true"; + +const vercelProjectSlug = (() => { + const candidate = process.env.CLIENT_URL || process.env.CLIENT_ORIGIN; + if (!candidate) return null; + const first = String(candidate).split(",")[0]?.trim(); + if (!first) return null; + + try { + const { hostname } = new URL(first); + if (!hostname.endsWith(".vercel.app")) return null; + return hostname.replace(/\.vercel\.app$/i, ""); + } catch (_e) { + return null; + } +})(); + +const allowedOrigins = [...envAllowed, "http://localhost:5000"].filter(Boolean); // Global Middlewares app.use( cors({ - origin: allowedOrigins, + origin: (origin, callback) => { + if (!origin) return callback(null, true); + + if (allowedOrigins.includes(origin)) return callback(null, true); + + if (vercelProjectSlug) { + try { + const { hostname, protocol } = new URL(origin); + if ( + protocol === "https:" && + hostname.endsWith(".vercel.app") && + (hostname === `${vercelProjectSlug}.vercel.app` || + hostname.startsWith(`${vercelProjectSlug}-`)) + ) { + return callback(null, true); + } + } catch (_e) { + // ignore invalid origin + } + } + + if (allowVercelPreviews) { + try { + const { hostname, protocol } = new URL(origin); + if (protocol === "https:" && hostname.endsWith(".vercel.app")) { + return callback(null, true); + } + } catch (_e) { + // ignore invalid origin + } + } + + return callback(new Error(`CORS blocked for origin: ${origin}`)); + }, credentials: true, }) ); @@ -35,7 +92,11 @@ app.use( secret: process.env.JWT_SECRET || "calendar-secret-key", resave: false, saveUninitialized: false, - cookie: { secure: false, maxAge: 600000 }, // 10 minutes + cookie: { + secure: isProd, + sameSite: isProd ? "none" : "lax", + maxAge: 600000, + }, }) ); From f3afb825c42208912bf6f54ad241d712c09b394f Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:08:07 +0200 Subject: [PATCH 22/25] Enhance Azure deployment workflow and update server configurations - Refactor Node.js setup in Azure workflow to use environment variable for version - Create deployment package for Azure deployment - Update server's package.json to specify Node.js engine version - Change default server port from 8080 to 4000 - Add health check endpoint for API monitoring --- .github/workflows/azure-deploy.yml | 8 ++++---- server/package.json | 3 +++ server/src/index.js | 2 +- server/src/routes/index.js | 9 +++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index 5ceba04d..83afb706 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -26,18 +26,18 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: "22.x" + node-version: ${{ env.NODE_VERSION }} cache: "npm" cache-dependency-path: server/package-lock.json - - name: Install dependencies + - name: Create deployment package run: | cd server - npm ci --production + zip -r ../server-deploy.zip . -x "node_modules/*" ".env" ".env.*" "**/*.log" - name: Deploy to Azure Web App uses: azure/webapps-deploy@v3 with: app-name: ${{ secrets.AZURE_WEBAPP_NAME }} publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} + package: ./server-deploy.zip diff --git a/server/package.json b/server/package.json index aa3d3db9..010a87c0 100644 --- a/server/package.json +++ b/server/package.json @@ -4,6 +4,9 @@ "description": "The backend server for the Eventy project", "main": "src/index.js", "type": "module", + "engines": { + "node": ">=22 <23" + }, "scripts": { "start": "node src/index.js", "dev": "nodemon src/index.js", diff --git a/server/src/index.js b/server/src/index.js index 8aedf38e..d25e4c4b 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -6,7 +6,7 @@ import { startReminderScheduler } from "./features/events/event.service.js"; import { initCertificateScheduler } from "./utils/certificate-scheduler.js"; import Feedback from "./features/feedback/feedback.model.js"; -const PORT = process.env.PORT || 8080; +const PORT = process.env.PORT || 4000; const startServer = async () => { // Connect to the database before starting the server diff --git a/server/src/routes/index.js b/server/src/routes/index.js index acb8afee..8ca2024a 100644 --- a/server/src/routes/index.js +++ b/server/src/routes/index.js @@ -22,6 +22,15 @@ router.get("/", (req, res) => { res.json({ message: "Welcome to the Eventy API!" }); }); +// Health check endpoint for uptime monitoring / deployment verification +router.get("/health", (_req, res) => { + res.json({ + ok: true, + service: "eventy-api", + timestamp: new Date().toISOString(), + }); +}); + // import authRoutes from '../features/auth/auth.route.js'; router.use("/auth", authRoutes); From b0af15f70d278ce2751489849a78394fc6fdd40f Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:34:35 +0200 Subject: [PATCH 23/25] Refactor logger initialization to use consola module and add error handling --- server/src/utils/logger.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/src/utils/logger.js b/server/src/utils/logger.js index 61cc76a7..57ab2014 100644 --- a/server/src/utils/logger.js +++ b/server/src/utils/logger.js @@ -16,7 +16,16 @@ * - apiLogger, authLogger, dbLogger, emailLogger, paymentLogger */ -import { createConsola } from "consola"; +import * as consola from "consola"; + +const createConsola = + consola.createConsola ?? consola.default?.createConsola ?? consola.default; + +if (typeof createConsola !== "function") { + throw new Error( + "Failed to initialize logger: consola.createConsola export not found" + ); +} const isDevelopment = process.env.NODE_ENV === "development"; const isProduction = process.env.NODE_ENV === "production"; From b48e25301ece1b0790ba753248ebbe4014a31cf6 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:40:12 +0200 Subject: [PATCH 24/25] Refactor logger initialization to enhance compatibility with CommonJS modules and improve fallback handling --- server/src/utils/logger.js | 51 ++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/server/src/utils/logger.js b/server/src/utils/logger.js index 57ab2014..9f0e894d 100644 --- a/server/src/utils/logger.js +++ b/server/src/utils/logger.js @@ -16,21 +16,51 @@ * - apiLogger, authLogger, dbLogger, emailLogger, paymentLogger */ -import * as consola from "consola"; +import * as consolaImport from "consola"; -const createConsola = - consola.createConsola ?? consola.default?.createConsola ?? consola.default; +const isDevelopment = process.env.NODE_ENV === "development"; +const isProduction = process.env.NODE_ENV === "production"; + +const consolaDefault = consolaImport?.default; +const consolaInstance = consolaImport.consola ?? consolaDefault; +const createConsolaFn = + consolaImport.createConsola ?? consolaDefault?.createConsola ?? null; -if (typeof createConsola !== "function") { - throw new Error( - "Failed to initialize logger: consola.createConsola export not found" - ); +// Create logger instance with environment-specific configuration. +// In some Azure/Oryx installs, consola may be resolved as a CommonJS module +// where `createConsola` isn't exposed as a named export. In that case, fall +// back to the default consola instance so the server can still boot. +export const logger = + (typeof createConsolaFn === "function" + ? createConsolaFn({ + level: isDevelopment ? 5 : 3, // 5=trace in dev, 3=info in prod + fancy: isDevelopment, // Colored output in development + formatOptions: { + colors: isDevelopment, + compact: isProduction, + date: true, // Always include timestamps on server + }, + }) + : consolaInstance) ?? console; + +if (!logger || typeof logger.info !== "function") { + // Extremely defensive fallback + // eslint-disable-next-line no-console + console.warn("Logger initialization fell back to console"); } -const isDevelopment = process.env.NODE_ENV === "development"; -const isProduction = process.env.NODE_ENV === "production"; +if (typeof logger.withTag !== "function") { + // Provide a minimal shim so existing code can keep calling withTag. + logger.withTag = () => logger; +} + +// If we're using the consola instance fallback, apply some of the same config. +if (logger?.options && typeof logger.options === "object") { + logger.options.level = isDevelopment ? 5 : 3; +} -// Create logger instance with environment-specific configuration +/* +// Previous code (kept for reference): export const logger = createConsola({ level: isDevelopment ? 5 : 3, // 5=trace in dev, 3=info in prod fancy: isDevelopment, // Colored output in development @@ -40,6 +70,7 @@ export const logger = createConsola({ date: true, // Always include timestamps on server }, }); +*/ // In production, be more conservative with logging and sanitize output if (isProduction) { From 0d7d30099ec77e9fb3775a7bd1bb6cb419f7f355 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:47:01 +0200 Subject: [PATCH 25/25] Update branch references in Azure and Vercel deployment workflows to feat/azure-vercel-deployment --- .github/workflows/azure-deploy.yml | 2 +- .github/workflows/vercel-deploy.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/azure-deploy.yml b/.github/workflows/azure-deploy.yml index 83afb706..cca1e740 100644 --- a/.github/workflows/azure-deploy.yml +++ b/.github/workflows/azure-deploy.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - feat/azure-deploy + - feat/azure-vercel-deployment paths: - "server/**" - ".github/workflows/azure-deploy.yml" diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index 541eadad..6e726249 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - feat/azure-deploy + - feat/azure-vercel-deployment paths: [ "client/**", "package.json", @@ -15,7 +15,7 @@ on: pull_request: branches: - main - - feat/azure-deploy + - feat/azure-vercel-deployment paths: [ "client/**",