diff --git a/api/.env.example b/api/.env.example index 8f50827..e11b26c 100644 --- a/api/.env.example +++ b/api/.env.example @@ -12,8 +12,10 @@ AUTH_CLIENT_ID= AUTH_CLIENT_SECRET= AUTH_REDIRECT_URI= AUTH_REVOCATION_URI= +AUTH_END_SESSION_URI= # AUTH_MODE=mock #uncomment this to bypass authentik # SERVER SETTINGS SERVER_PORT=3000 -CLIENT_URL= # This is whatever URL the client web app is served on \ No newline at end of file +CLIENT_URL= # This is whatever URL the client web app is served on +CLIENT_DOMAIN= #whatever.com \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 36aedd2..a05eac3 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -24,6 +24,7 @@ "@types/express": "^5.0.3", "@types/morgan": "^1.9.10", "@types/node": "^24.8.1", + "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } }, @@ -34,6 +35,44 @@ "license": "MIT", "optional": true }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -272,6 +311,20 @@ "node": ">=8" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/aproba": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", @@ -294,6 +347,16 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -348,6 +411,19 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -399,6 +475,19 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -504,6 +593,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -533,6 +647,16 @@ "color-support": "bin.js" } }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -685,6 +809,19 @@ "node": ">=8" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dotenv": { "version": "17.2.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", @@ -905,12 +1042,52 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", @@ -971,6 +1148,21 @@ "license": "ISC", "optional": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1047,6 +1239,19 @@ "node": ">= 0.4" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1075,6 +1280,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1228,6 +1467,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1298,6 +1547,29 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1308,6 +1580,19 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -1315,6 +1600,16 @@ "license": "MIT", "optional": true }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -1458,6 +1753,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -1675,6 +1994,20 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/mysql2": { "version": "3.14.3", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.3.tgz", @@ -1790,6 +2123,16 @@ "node": ">=6" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -1951,11 +2294,47 @@ "node": ">=16" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -2041,6 +2420,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -2103,6 +2513,29 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -2113,6 +2546,17 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2146,6 +2590,30 @@ "node": ">= 18" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2363,6 +2831,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -2565,6 +3043,19 @@ "node": ">=8" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2574,6 +3065,28 @@ "node": ">=0.6" } }, + "node_modules/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/api/package.json b/api/package.json index 19d5e14..48faf33 100644 --- a/api/package.json +++ b/api/package.json @@ -8,7 +8,8 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "tsc && node ./built/api/src/index.js" + "dev": "tsc && tsc-alias && node ./built/api/src/index.js", + "build" : "tsc && tsc-alias" }, "dependencies": { "connect-sqlite3": "^0.9.16", @@ -26,6 +27,7 @@ "@types/express": "^5.0.3", "@types/morgan": "^1.9.10", "@types/node": "^24.8.1", + "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } } diff --git a/api/src/index.js b/api/src/index.js index 59c1381..1a793aa 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -8,7 +8,7 @@ const app = express() app.use(morgan('dev')) app.use(cors({ - origin: ['https://aj17thdev.nexuszone.net', 'http://localhost:5173'], // your SPA origins + origin: [process.env.CLIENT_URL], // your SPA origins credentials: true })); @@ -32,7 +32,7 @@ app.use(session({ cookie: { httpOnly: true, sameSite: 'lax', - domain: 'nexuszone.net' + domain: process.env.CLIENT_DOMAIN } })); app.use(passport.authenticate('session')); @@ -46,6 +46,7 @@ const { status, memberStatus } = require('./routes/statuses') const authRouter = require('./routes/auth') const { roles, memberRoles } = require('./routes/roles'); const { courseRouter, eventRouter } = require('./routes/course'); +const { calendarRouter } = require('./routes/calendar') const morgan = require('morgan'); app.use('/application', applicationsRouter); @@ -59,6 +60,7 @@ app.use('/roles', roles) app.use('/memberRoles', memberRoles) app.use('/course', courseRouter) app.use('/courseEvent', eventRouter) +app.use('/calendar', calendarRouter) app.use('/', authRouter) app.get('/ping', (req, res) => { diff --git a/api/src/routes/calendar.ts b/api/src/routes/calendar.ts index 2154eec..ff486cb 100644 --- a/api/src/routes/calendar.ts +++ b/api/src/routes/calendar.ts @@ -1,4 +1,6 @@ -import { getEventAttendance, getEventDetails, getShortEventsInRange } from "../services/calendarService"; +import { Request, Response } from "express"; +import { createEvent, getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus, setEventCancelled, updateEvent } from "../services/calendarService"; +import { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar"; const express = require('express'); const r = express.Router(); @@ -9,42 +11,108 @@ function addMonths(date: Date, months: number): Date { return d } -//get calendar events paged +//get calendar events paged, requires a query string with from= and to= as mariadb ISO strings r.get('/', async (req, res) => { - const viewDate: Date = req.body.date; - //generate date range - const backDate: Date = addMonths(viewDate, -1); - const frontDate: Date = addMonths(viewDate, 2); + try { + const fromDate: string = req.query.from; + const toDate: string = req.query.to; - const events = getShortEventsInRange(backDate, frontDate); + if (fromDate === undefined || toDate === undefined) { + res.status(400).send("Missing required query parameters 'from' and 'to'"); + return; + } - res.status(200).json(events); + const events = await getShortEventsInRange(fromDate, toDate); + + res.status(200).json(events); + } catch (error) { + console.error('Error fetching calendar events:', error); + res.status(500).send('Error fetching calendar events'); + } }); r.get('/upcoming', async (req, res) => { res.sendStatus(501); }) -//get event details -r.get('/:id', async (req, res) => { +r.post('/:id/cancel', async (req: Request, res: Response) => { try { - const eventID: number = req.params.id; + const eventID = Number(req.params.id); + setEventCancelled(eventID, true); + res.sendStatus(200); + } catch (error) { + console.error('Error setting cancel status:', error); + res.status(500).send('Error setting cancel status'); + } +}) +r.post('/:id/uncancel', async (req: Request, res: Response) => { + try { + const eventID = Number(req.params.id); + setEventCancelled(eventID, false); + res.sendStatus(200); + } catch (error) { + console.error('Error setting cancel status:', error); + res.status(500).send('Error setting cancel status'); + } +}) - let details = getEventDetails(eventID); - let attendance = await getEventAttendance(eventID); - let out = { ...details, attendance } - console.log(out); - res.status(200).json(out); +r.post('/:id/attendance', async (req: Request, res: Response) => { + try { + let member = req.user.id; + let event = Number(req.params.id); + let state = req.query.state as CalendarAttendance; + setAttendanceStatus(member, event, state); + res.sendStatus(200); + } catch (error) { + console.error('Failed to set attendance:', error); + res.status(500).json(error); + } +}) +//get event details +r.get('/:id', async (req: Request, res: Response) => { + try { + const eventID: number = Number(req.params.id); + + let details: CalendarEvent = await getEventDetails(eventID); + details.eventSignups = await getEventAttendance(eventID); + res.status(200).json(details); } catch (err) { console.error('Insert failed:', err); res.status(500).json(err); } }) -//post a new calendar event -r.post('/', async (req, res) => { +//post a new calendar event +r.post('/', async (req: Request, res: Response) => { + try { + const member = req.user.id; + let event: CalendarEvent = req.body; + event.creator_id = member; + event.start = new Date(event.start); + event.end = new Date(event.end); + createEvent(event); + res.sendStatus(200); + } catch (error) { + console.error('Failed to create event:', error); + res.status(500).json(error); + } }) -module.exports.calendar = r; \ No newline at end of file +r.put('/', async (req: Request, res: Response) => { + try { + let event: CalendarEvent = req.body; + event.start = new Date(event.start); + event.end = new Date(event.end); + console.log(event); + updateEvent(event); + res.sendStatus(200); + } catch (error) { + console.error('Failed to update event:', error); + res.status(500).json(error); + } +}) + + +module.exports.calendarRouter = r; \ No newline at end of file diff --git a/api/src/services/calendarService.d.ts b/api/src/services/calendarService.d.ts deleted file mode 100644 index 6273805..0000000 --- a/api/src/services/calendarService.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export declare function createEvent(eventObject: any): Promise; -export declare function updateEvent(eventObject: any): Promise; -export declare function cancelEvent(eventID: any): Promise; -export declare function getShortEventsInRange(startDate: any, endDate: any): Promise; -export declare function getEventDetailed(eventID: any): Promise; -//# sourceMappingURL=calendarService.d.ts.map \ No newline at end of file diff --git a/api/src/services/calendarService.d.ts.map b/api/src/services/calendarService.d.ts.map deleted file mode 100644 index 156c9d6..0000000 --- a/api/src/services/calendarService.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"calendarService.d.ts","sourceRoot":"","sources":["calendarService.ts"],"names":[],"mappings":"AAEA,wBAAsB,WAAW,CAAC,WAAW,KAAA,iBAE5C;AAED,wBAAsB,WAAW,CAAC,WAAW,KAAA,iBAE5C;AAED,wBAAsB,WAAW,CAAC,OAAO,KAAA,iBAExC;AAED,wBAAsB,qBAAqB,CAAC,SAAS,KAAA,EAAE,OAAO,KAAA,iBAE7D;AAED,wBAAsB,gBAAgB,CAAC,OAAO,KAAA,iBAE7C"} \ No newline at end of file diff --git a/api/src/services/calendarService.js b/api/src/services/calendarService.js deleted file mode 100644 index ee8e08e..0000000 --- a/api/src/services/calendarService.js +++ /dev/null @@ -1,19 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createEvent = createEvent; -exports.updateEvent = updateEvent; -exports.cancelEvent = cancelEvent; -exports.getShortEventsInRange = getShortEventsInRange; -exports.getEventDetailed = getEventDetailed; -const pool = require('../db'); -async function createEvent(eventObject) { -} -async function updateEvent(eventObject) { -} -async function cancelEvent(eventID) { -} -async function getShortEventsInRange(startDate, endDate) { -} -async function getEventDetailed(eventID) { -} -//# sourceMappingURL=calendarService.js.map \ No newline at end of file diff --git a/api/src/services/calendarService.js.map b/api/src/services/calendarService.js.map deleted file mode 100644 index e8a02ee..0000000 --- a/api/src/services/calendarService.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"calendarService.js","sourceRoot":"","sources":["calendarService.ts"],"names":[],"mappings":";;AAEA,kCAEC;AAED,kCAEC;AAED,kCAEC;AAED,sDAEC;AAED,4CAEC;AApBD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAEtB,KAAK,UAAU,WAAW,CAAC,WAAW;AAE7C,CAAC;AAEM,KAAK,UAAU,WAAW,CAAC,WAAW;AAE7C,CAAC;AAEM,KAAK,UAAU,WAAW,CAAC,OAAO;AAEzC,CAAC;AAEM,KAAK,UAAU,qBAAqB,CAAC,SAAS,EAAE,OAAO;AAE9D,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,OAAO;AAE9C,CAAC"} \ No newline at end of file diff --git a/api/src/services/calendarService.ts b/api/src/services/calendarService.ts index 890889c..c88f671 100644 --- a/api/src/services/calendarService.ts +++ b/api/src/services/calendarService.ts @@ -1,26 +1,12 @@ import pool from '../db'; - -export interface CalendarEvent { - id: number; - name: string; - start: Date; // DATETIME -> Date - end: Date; // DATETIME -> Date - location: string; - color: string; // 7 character hex string - description?: string | null; - creator?: number | null; // foreign key to members.id, nullable - cancelled: boolean; // TINYINT(1) -> boolean - created_at: Date; // TIMESTAMP -> Date - updated_at: Date; // TIMESTAMP -> Date -} - -export type Attendance = 'attending' | 'maybe' | 'not_attending'; +import { CalendarEventShort, CalendarSignup, CalendarEvent, CalendarAttendance } from "@app/shared/types/calendar" +import { toDateTime } from "@app/shared/utils/time" export async function createEvent(eventObject: Omit) { const sql = ` INSERT INTO calendar_events (name, start, end, location, color, description, creator) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?) `; const params = [ eventObject.name, @@ -29,7 +15,7 @@ export async function createEvent(eventObject: Omit { const sql = ` - SELECT id, name, start, end, color + SELECT id, name, start, end, color, cancelled, full_day FROM calendar_events WHERE start BETWEEN ? AND ? ORDER BY start ASC `; - return await pool.query(sql, [startDate, endDate]); + const res: CalendarEventShort[] = await pool.query(sql, [startDate, endDate]); + return res; } -export async function getEventDetails(eventID: number) { +export async function getEventDetails(eventID: number): Promise { const sql = ` SELECT e.id, @@ -101,14 +88,14 @@ export async function getEventDetails(eventID: number) { e.cancelled, e.created_at, e.updated_at, - m.id AS creator_id, + e.creator AS creator_id, m.name AS creator_name FROM calendar_events e LEFT JOIN members m ON e.creator = m.id WHERE e.id = ? `; - - return await pool.query(sql, [eventID]) + let vals: CalendarEvent[] = await pool.query(sql, [eventID]); + return vals[0]; } export async function getUpcomingEvents(date: Date, limit: number) { @@ -124,7 +111,7 @@ export async function getUpcomingEvents(date: Date, limit: number) { } -export async function setAttendanceStatus(memberID: number, eventID: number, status: Attendance) { +export async function setAttendanceStatus(memberID: number, eventID: number, status: CalendarAttendance) { const sql = ` INSERT INTO calendar_events_signups (member_id, event_id, status) VALUES (?, ?, ?) @@ -135,7 +122,7 @@ export async function setAttendanceStatus(memberID: number, eventID: number, sta return { success: true } } -export async function getEventAttendance(eventID: number) { +export async function getEventAttendance(eventID: number): Promise { const sql = ` SELECT s.member_id, diff --git a/shared/schemas/calendarEventSchema.ts b/shared/schemas/calendarEventSchema.ts new file mode 100644 index 0000000..c0b4893 --- /dev/null +++ b/shared/schemas/calendarEventSchema.ts @@ -0,0 +1,33 @@ +import z from "zod"; + +const dateRe = /^\d{4}-\d{2}-\d{2}$/ // YYYY-MM-DD +const timeRe = /^(?:[01]\d|2[0-3]):[0-5]\d$/ // HH:mm (24h)\ + +export function parseLocalDateTime(dateStr: string, timeStr: string) { + // Construct a Date in the user's local timezone + const [y, m, d] = dateStr.split("-").map(Number) + const [hh, mm] = timeStr.split(":").map(Number) + return new Date(y, m - 1, d, hh, mm, 0, 0) +} + +export const calendarEventSchema = z.object({ + name: z.string().min(2, "Please enter at least 2 characters").max(100), + startDate: z.string().regex(dateRe, "Use YYYY-MM-DD"), + startTime: z.string().regex(timeRe, "Use HH:mm (24h)"), + endDate: z.string().regex(dateRe, "Use YYYY-MM-DD"), + endTime: z.string().regex(timeRe, "Use HH:mm (24h)"), + location: z.string().max(200).default(""), + color: z.string().regex(/^#([0-9A-Fa-f]{6})$/, "Use a hex color like #AABBCC"), + description: z.string().max(2000).default(""), + id: z.number().int().nonnegative().nullable().default(null), +}).superRefine((vals, ctx) => { + const start = parseLocalDateTime(vals.startDate, vals.startTime) + const end = parseLocalDateTime(vals.endDate, vals.endTime) + if (!(end > start)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "End must be after start", + path: ["endTime"], // attach to a visible field + }) + } +}) \ No newline at end of file diff --git a/shared/types/calendar.ts b/shared/types/calendar.ts new file mode 100644 index 0000000..526068f --- /dev/null +++ b/shared/types/calendar.ts @@ -0,0 +1,39 @@ +export interface CalendarEvent { + id?: number; + name: string; + start: Date; + end: Date; + location: string; + color: string; + description: string; + creator_id?: number; + cancelled?: boolean; + created_at?: Date; + updated_at?: Date; + + creator_name?: string | null; + eventSignups?: CalendarSignup[] | null; +} + +export enum CalendarAttendance { + Attending = "attending", + NotAttending = "not_attending", + Maybe = "maybe" +} + +export interface CalendarSignup { + member_id: number; + eventID: number; + status: CalendarAttendance; + member_name?: string; +} + +export interface CalendarEventShort { + id: number; + name: string; + start: Date; + end: Date; + color: string; + cancelled: boolean; + full_day: boolean; +} \ No newline at end of file diff --git a/shared/utils/time.ts b/shared/utils/time.ts new file mode 100644 index 0000000..98d550b --- /dev/null +++ b/shared/utils/time.ts @@ -0,0 +1,11 @@ +export function toDateTime(date: Date): string { + // This produces a CST-local time because server runs in CST + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + const hour = date.getHours().toString().padStart(2, "0"); + const minute = date.getMinutes().toString().padStart(2, "0"); + const second = date.getSeconds().toString().padStart(2, "0"); + + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} \ No newline at end of file diff --git a/ui/src/api/calendar.ts b/ui/src/api/calendar.ts index d4c01ac..91fa66f 100644 --- a/ui/src/api/calendar.ts +++ b/ui/src/api/calendar.ts @@ -1,13 +1,13 @@ -export interface CalendarEvent { - name: string, - start: Date, - end: Date, - location: string, - color: string, - description: string, - creator: any | null, // user object - id: number | null -} +// export interface CalendarEvent { +// name: string, +// start: Date, +// end: Date, +// location: string, +// color: string, +// description: string, +// creator: any | null, // user object +// id: number | null +// } export enum CalendarAttendance { Attending = "attending", @@ -21,22 +21,107 @@ export interface CalendarSignup { state: CalendarAttendance } -export async function createCalendarEvent(eventData: CalendarEvent) { +import { CalendarEventShort, CalendarEvent } from "@shared/types/calendar"; +//@ts-ignore +const addr = import.meta.env.VITE_APIHOST; + +export async function getMonthCalendarEvents(viewedMonth: Date): Promise { + + const year = viewedMonth.getFullYear(); + const month = viewedMonth.getMonth(); + + // Base range: first and last day of the month + const firstOfMonth = new Date(year, month, 1); + const lastOfMonth = new Date(year, month + 1, 0); + + // --- Apply 10 day padding --- + const start = new Date(firstOfMonth); + start.setDate(start.getDate() - 10); + + const end = new Date(lastOfMonth); + end.setDate(end.getDate() + 10); + end.setHours(23, 59, 59, 999); + + const from = start.toISOString(); + const to = end.toISOString(); + + const url = `${addr}/calendar?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`; + + const res = await fetch(url); + + if (!res.ok) { + throw new Error(`Failed to fetch events: ${res.status} ${res.statusText}`); + } + + return res.json(); +} + +export async function getCalendarEvent(id: number): Promise { + let res = await fetch(`${addr}/calendar/${id}`); + + if (res.ok) { + return await res.json(); + } else { + throw new Error(`Failed to fetch event: ${res.status} ${res.statusText}`); + } +} + +export async function createCalendarEvent(eventData: CalendarEvent) { + let res = await fetch(`${addr}/calendar`, { + method: "POST", + credentials: "include", + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(eventData) + }); + + if (res.ok) { + return; + } else { + throw new Error(`Failed to set attendance: ${res.status} ${res.statusText}`); + } } export async function editCalendarEvent(eventData: CalendarEvent) { + let res = await fetch(`${addr}/calendar`, { + method: "PUT", + credentials: "include", + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(eventData) + }); + if (res.ok) { + return; + } else { + throw new Error(`Failed to set attendance: ${res.status} ${res.statusText}`); + } } -export async function cancelCalendarEvent(eventID: number) { +export async function setCancelCalendarEvent(eventID: number, cancel: boolean) { + let route = cancel ? "cancel" : "uncancel"; -} + console.log(route); + let res = await fetch(`${addr}/calendar/${eventID}/${route}`, { + method: "POST", + credentials: "include" + }); -export async function adminCancelCalendarEvent(eventID: number) { - + if (res.ok) { + return; + } else { + throw new Error(`Failed to set attendance: ${res.status} ${res.statusText}`); + } } export async function setCalendarEventAttendance(eventID: number, state: CalendarAttendance) { + let res = await fetch(`${addr}/calendar/${eventID}/attendance?state=${state}`, { + method: "POST", + credentials: "include", + }); + if (res.ok) { + return; + } else { + throw new Error(`Failed to set attendance: ${res.status} ${res.statusText}`); + } } \ No newline at end of file diff --git a/ui/src/components/calendar/CreateCalendarEvent.vue b/ui/src/components/calendar/CreateCalendarEvent.vue index d2e5355..a898b98 100644 --- a/ui/src/components/calendar/CreateCalendarEvent.vue +++ b/ui/src/components/calendar/CreateCalendarEvent.vue @@ -1,6 +1,6 @@