From 42bad8c6c8dbf490bd0e89a55c5f892ebb609e1c Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Sat, 5 Jul 2025 15:43:23 -0700 Subject: [PATCH] feat: set up announcement and cors logic for server --- pnpm-lock.yaml | 299 ++++++++++++++++-------- server/dev.env | 5 + server/eslint.config.js | 15 +- server/package.json | 6 +- server/prisma/schema.prisma | 19 ++ server/prod.env | 4 + server/src/config/routesWithoutCors.ts | 15 ++ server/src/db/database.ts | 24 ++ server/src/hooks/cors.ts | 28 +++ server/src/index.ts | 12 +- server/src/modules/announceOnDiscord.ts | 65 ++++++ server/src/modules/announceOnForum.ts | 40 ++++ server/src/routes/announcement.ts | 95 ++++++++ server/src/routes/base.ts | 23 ++ 14 files changed, 548 insertions(+), 102 deletions(-) create mode 100644 server/dev.env create mode 100644 server/prisma/schema.prisma create mode 100644 server/src/config/routesWithoutCors.ts create mode 100644 server/src/db/database.ts create mode 100644 server/src/hooks/cors.ts create mode 100644 server/src/modules/announceOnDiscord.ts create mode 100644 server/src/modules/announceOnForum.ts create mode 100644 server/src/routes/announcement.ts create mode 100644 server/src/routes/base.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2d19d4..150305d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,13 +10,13 @@ importers: devDependencies: '@nhcarrigan/eslint-config': specifier: 5.2.0 - version: 5.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7))(playwright@1.53.2)(react@19.1.0)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)) + version: 5.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(playwright@1.53.2)(react@19.1.0)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3)) '@nhcarrigan/typescript-config': specifier: 4.0.0 version: 4.0.0(typescript@5.8.3) eslint: specifier: 9.30.1 - version: 9.30.1(jiti@1.21.7) + version: 9.30.1(jiti@2.4.2) turbo: specifier: 2.5.4 version: 2.5.4 @@ -56,7 +56,7 @@ importers: devDependencies: '@angular/build': specifier: 20.0.5 - version: 20.0.5(@angular/compiler-cli@20.0.6(@angular/compiler@20.0.6)(typescript@5.8.3))(@angular/compiler@20.0.6)(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.6(@angular/common@20.0.6(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.10)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.3.0)(postcss@8.5.6)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)) + version: 20.0.5(@angular/compiler-cli@20.0.6(@angular/compiler@20.0.6)(typescript@5.8.3))(@angular/compiler@20.0.6)(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.6(@angular/common@20.0.6(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.10)(chokidar@4.0.3)(jiti@2.4.2)(karma@6.4.4)(less@4.3.0)(postcss@8.5.6)(terser@5.39.1)(tslib@2.8.1)(tsx@4.20.3)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3)) '@angular/cli': specifier: 20.0.5 version: 20.0.5(@types/node@24.0.10)(chokidar@4.0.3) @@ -93,6 +93,9 @@ importers: '@nhcarrigan/logger': specifier: 1.0.0 version: 1.0.0 + '@prisma/client': + specifier: 6.11.1 + version: 6.11.1(prisma@6.11.1(typescript@5.8.3))(typescript@5.8.3) fastify: specifier: 5.4.0 version: 5.4.0 @@ -100,6 +103,12 @@ importers: '@types/node': specifier: 24.0.10 version: 24.0.10 + prisma: + specifier: 6.11.1 + version: 6.11.1(typescript@5.8.3) + tsx: + specifier: 4.20.3 + version: 4.20.3 packages: @@ -1074,6 +1083,36 @@ packages: resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@prisma/client@6.11.1': + resolution: {integrity: sha512-5CLFh8QP6KxRm83pJ84jaVCeSVPQr8k0L2SEtOJHwdkS57/VQDcI/wQpGmdyOZi+D9gdNabdo8tj1Uk+w+upsQ==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.11.1': + resolution: {integrity: sha512-z6rCTQN741wxDq82cpdzx2uVykpnQIXalLhrWQSR0jlBVOxCIkz3HZnd8ern3uYTcWKfB3IpVAF7K2FU8t/8AQ==} + + '@prisma/debug@6.11.1': + resolution: {integrity: sha512-lWRb/YSWu8l4Yum1UXfGLtqFzZkVS2ygkWYpgkbgMHn9XJlMITIgeMvJyX5GepChzhmxuSuiq/MY/kGFweOpGw==} + + '@prisma/engines-version@6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9': + resolution: {integrity: sha512-swFJTOOg4tHyOM1zB/pHb3MeH0i6t7jFKn5l+ZsB23d9AQACuIRo9MouvuKGvnDogzkcjbWnXi/NvOZ0+n5Jfw==} + + '@prisma/engines@6.11.1': + resolution: {integrity: sha512-6eKEcV6V8W2eZAUwX2xTktxqPM4vnx3sxz3SDtpZwjHKpC6lhOtc4vtAtFUuf5+eEqBk+dbJ9Dcaj6uQU+FNNg==} + + '@prisma/fetch-engine@6.11.1': + resolution: {integrity: sha512-NBYzmkXTkj9+LxNPRSndaAeALOL1Gr3tjvgRYNqruIPlZ6/ixLeuE/5boYOewant58tnaYFZ5Ne0jFBPfGXHpQ==} + + '@prisma/get-platform@6.11.1': + resolution: {integrity: sha512-b2Z8oV2gwvdCkFemBTFd0x4lsL4O2jLSx8lB7D+XqoFALOQZPa7eAPE1NU0Mj1V8gPHRxIsHnyUNtw2i92psUw==} + '@rollup/rollup-android-arm-eabi@4.40.2': resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} cpu: [arm] @@ -2385,6 +2424,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2741,8 +2783,8 @@ packages: jasmine-core@5.8.0: resolution: {integrity: sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA==} - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true js-tokens@4.0.0: @@ -3353,6 +3395,16 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prisma@6.11.1: + resolution: {integrity: sha512-VzJToRlV0s9Vu2bfqHiRJw73hZNCG/AyJeX+kopbu4GATTjTUdEWUteO3p4BLYoHpMS4o8pD3v6tF44BHNZI1w==} + engines: {node: '>=18.18'} + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + proc-log@5.0.0: resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -3467,6 +3519,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -3869,6 +3924,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + tuf-js@3.1.0: resolution: {integrity: sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -4281,7 +4341,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular/build@20.0.5(@angular/compiler-cli@20.0.6(@angular/compiler@20.0.6)(typescript@5.8.3))(@angular/compiler@20.0.6)(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.6(@angular/common@20.0.6(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.10)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.3.0)(postcss@8.5.6)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1))': + '@angular/build@20.0.5(@angular/compiler-cli@20.0.6(@angular/compiler@20.0.6)(typescript@5.8.3))(@angular/compiler@20.0.6)(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.6(@angular/common@20.0.6(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.10)(chokidar@4.0.3)(jiti@2.4.2)(karma@6.4.4)(less@4.3.0)(postcss@8.5.6)(terser@5.39.1)(tslib@2.8.1)(tsx@4.20.3)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3))': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2000.5(chokidar@4.0.3) @@ -4291,7 +4351,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.1 '@babel/helper-split-export-declaration': 7.24.7 '@inquirer/confirm': 5.1.10(@types/node@24.0.10) - '@vitejs/plugin-basic-ssl': 2.0.0(vite@6.3.5(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)) + '@vitejs/plugin-basic-ssl': 2.0.0(vite@6.3.5(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3)) beasties: 0.3.4 browserslist: 4.25.1 esbuild: 0.25.5 @@ -4311,7 +4371,7 @@ snapshots: tinyglobby: 0.2.13 tslib: 2.8.1 typescript: 5.8.3 - vite: 6.3.5(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vite: 6.3.5(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) watchpack: 2.4.2 optionalDependencies: '@angular/core': 20.0.6(@angular/compiler@20.0.6)(rxjs@7.8.2)(zone.js@0.15.1) @@ -4320,7 +4380,7 @@ snapshots: less: 4.3.0 lmdb: 3.3.0 postcss: 8.5.6 - vitest: 3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vitest: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - chokidar @@ -4662,22 +4722,22 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.4.1(eslint@9.30.1(jiti@1.21.7))': + '@eslint-community/eslint-plugin-eslint-comments@4.4.1(eslint@9.30.1(jiti@2.4.2))': dependencies: escape-string-regexp: 4.0.0 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) ignore: 5.3.2 - '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': dependencies: - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.2.4(eslint@9.30.1(jiti@1.21.7))': + '@eslint/compat@1.2.4(eslint@9.30.1(jiti@2.4.2))': optionalDependencies: - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) '@eslint/config-array@0.21.0': dependencies: @@ -5046,29 +5106,29 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.0.2 optional: true - '@nhcarrigan/eslint-config@5.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7))(playwright@1.53.2)(react@19.1.0)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1))': + '@nhcarrigan/eslint-config@5.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(playwright@1.53.2)(react@19.1.0)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3))': dependencies: - '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.30.1(jiti@1.21.7)) - '@eslint/compat': 1.2.4(eslint@9.30.1(jiti@1.21.7)) + '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.30.1(jiti@2.4.2)) + '@eslint/compat': 1.2.4(eslint@9.30.1(jiti@2.4.2)) '@eslint/eslintrc': 3.2.0 '@eslint/js': 9.17.0 - '@stylistic/eslint-plugin': 2.12.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/eslint-plugin': 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - '@vitest/eslint-plugin': 1.1.24(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)) - eslint: 9.30.1(jiti@1.21.7) - eslint-plugin-deprecation: 3.0.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7)) - eslint-plugin-jsdoc: 50.6.1(eslint@9.30.1(jiti@1.21.7)) - eslint-plugin-playwright: 2.1.0(eslint@9.30.1(jiti@1.21.7)) - eslint-plugin-react: 7.37.3(eslint@9.30.1(jiti@1.21.7)) + '@stylistic/eslint-plugin': 2.12.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@vitest/eslint-plugin': 1.1.24(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3)) + eslint: 9.30.1(jiti@2.4.2) + eslint-plugin-deprecation: 3.0.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-jsdoc: 50.6.1(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-playwright: 2.1.0(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-react: 7.37.3(eslint@9.30.1(jiti@2.4.2)) eslint-plugin-sort-keys-fix: 1.1.2 - eslint-plugin-unicorn: 56.0.1(eslint@9.30.1(jiti@1.21.7)) + eslint-plugin-unicorn: 56.0.1(eslint@9.30.1(jiti@2.4.2)) globals: 15.14.0 playwright: 1.53.2 react: 19.1.0 typescript: 5.8.3 - vitest: 3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vitest: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) transitivePeerDependencies: - '@typescript-eslint/utils' - eslint-import-resolver-typescript @@ -5218,6 +5278,36 @@ snapshots: '@pkgr/core@0.1.2': {} + '@prisma/client@6.11.1(prisma@6.11.1(typescript@5.8.3))(typescript@5.8.3)': + optionalDependencies: + prisma: 6.11.1(typescript@5.8.3) + typescript: 5.8.3 + + '@prisma/config@6.11.1': + dependencies: + jiti: 2.4.2 + + '@prisma/debug@6.11.1': {} + + '@prisma/engines-version@6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9': {} + + '@prisma/engines@6.11.1': + dependencies: + '@prisma/debug': 6.11.1 + '@prisma/engines-version': 6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9 + '@prisma/fetch-engine': 6.11.1 + '@prisma/get-platform': 6.11.1 + + '@prisma/fetch-engine@6.11.1': + dependencies: + '@prisma/debug': 6.11.1 + '@prisma/engines-version': 6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9 + '@prisma/get-platform': 6.11.1 + + '@prisma/get-platform@6.11.1': + dependencies: + '@prisma/debug': 6.11.1 + '@rollup/rollup-android-arm-eabi@4.40.2': optional: true @@ -5382,10 +5472,10 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@stylistic/eslint-plugin@2.12.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)': + '@stylistic/eslint-plugin@2.12.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.30.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -5427,15 +5517,15 @@ snapshots: '@types/normalize-package-data@2.4.4': {} - '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.19.0 - '@typescript-eslint/type-utils': 8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/utils': 8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.19.0 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -5444,14 +5534,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.19.0 '@typescript-eslint/types': 8.19.0 '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.19.0 debug: 4.4.1 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -5484,12 +5574,12 @@ snapshots: dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) ts-api-utils: 1.4.3(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -5546,35 +5636,35 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/utils@7.18.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3) - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/utils@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@typescript-eslint/scope-manager': 8.19.0 '@typescript-eslint/types': 8.19.0 '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.8.3) - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@typescript-eslint/scope-manager': 8.35.1 '@typescript-eslint/types': 8.35.1 '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -5594,17 +5684,17 @@ snapshots: '@typescript-eslint/types': 8.35.1 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-basic-ssl@2.0.0(vite@6.3.5(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1))': + '@vitejs/plugin-basic-ssl@2.0.0(vite@6.3.5(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3))': dependencies: - vite: 6.3.5(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vite: 6.3.5(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) - '@vitest/eslint-plugin@1.1.24(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1))': + '@vitest/eslint-plugin@1.1.24(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3))': dependencies: - '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.30.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) optionalDependencies: typescript: 5.8.3 - vitest: 3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vitest: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) '@vitest/expect@3.2.4': dependencies: @@ -5614,13 +5704,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1))': + '@vitest/mocker@3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 7.0.1(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -6359,27 +6449,27 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@1.21.7)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.30.1(jiti@1.21.7) + '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-deprecation@3.0.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-deprecation@3.0.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/utils': 7.18.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.30.1(jiti@1.21.7) + '@typescript-eslint/utils': 7.18.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) ts-api-utils: 1.4.3(typescript@5.8.3) tslib: 2.8.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint@9.30.1(jiti@1.21.7)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -6388,9 +6478,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@1.21.7)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6402,20 +6492,20 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsdoc@50.6.1(eslint@9.30.1(jiti@1.21.7)): + eslint-plugin-jsdoc@50.6.1(eslint@9.30.1(jiti@2.4.2)): dependencies: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) espree: 10.4.0 esquery: 1.6.0 parse-imports: 2.2.1 @@ -6425,12 +6515,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-playwright@2.1.0(eslint@9.30.1(jiti@1.21.7)): + eslint-plugin-playwright@2.1.0(eslint@9.30.1(jiti@2.4.2)): dependencies: - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) globals: 13.24.0 - eslint-plugin-react@7.37.3(eslint@9.30.1(jiti@1.21.7)): + eslint-plugin-react@7.37.3(eslint@9.30.1(jiti@2.4.2)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -6438,7 +6528,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -6459,14 +6549,14 @@ snapshots: natural-compare: 1.4.0 requireindex: 1.2.0 - eslint-plugin-unicorn@56.0.1(eslint@9.30.1(jiti@1.21.7)): + eslint-plugin-unicorn@56.0.1(eslint@9.30.1(jiti@2.4.2)): dependencies: '@babel/helper-validator-identifier': 7.27.1 - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) ci-info: 4.2.0 clean-regexp: 1.0.0 core-js-compat: 3.43.0 - eslint: 9.30.1(jiti@1.21.7) + eslint: 9.30.1(jiti@2.4.2) esquery: 1.6.0 globals: 15.14.0 indent-string: 4.0.0 @@ -6490,9 +6580,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.30.1(jiti@1.21.7): + eslint@9.30.1(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.0 @@ -6528,7 +6618,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 1.21.7 + jiti: 2.4.2 transitivePeerDependencies: - supports-color @@ -6754,6 +6844,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7132,8 +7226,7 @@ snapshots: jasmine-core@5.8.0: {} - jiti@1.21.7: - optional: true + jiti@2.4.2: {} js-tokens@4.0.0: {} @@ -7838,6 +7931,13 @@ snapshots: prelude-ls@1.2.1: {} + prisma@6.11.1(typescript@5.8.3): + dependencies: + '@prisma/config': 6.11.1 + '@prisma/engines': 6.11.1 + optionalDependencies: + typescript: 5.8.3 + proc-log@5.0.0: {} process-warning@4.0.1: {} @@ -7944,6 +8044,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -8452,6 +8554,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.20.3: + dependencies: + esbuild: 0.25.5 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + tuf-js@3.1.0: dependencies: '@tufjs/models': 3.0.1 @@ -8583,13 +8692,13 @@ snapshots: vary@1.1.2: {} - vite-node@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1): + vite-node@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.1(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -8604,7 +8713,7 @@ snapshots: - tsx - yaml - vite@6.3.5(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1): + vite@6.3.5(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -8615,12 +8724,13 @@ snapshots: optionalDependencies: '@types/node': 24.0.10 fsevents: 2.3.3 - jiti: 1.21.7 + jiti: 2.4.2 less: 4.3.0 sass: 1.88.0 terser: 5.39.1 + tsx: 4.20.3 - vite@7.0.1(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1): + vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -8631,16 +8741,17 @@ snapshots: optionalDependencies: '@types/node': 24.0.10 fsevents: 2.3.3 - jiti: 1.21.7 + jiti: 2.4.2 less: 4.3.0 sass: 1.88.0 terser: 5.39.1 + tsx: 4.20.3 - vitest@3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1): + vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)) + '@vitest/mocker': 3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -8658,8 +8769,8 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.1(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) - vite-node: 3.2.4(@types/node@24.0.10)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.1) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(less@4.3.0)(sass@1.88.0)(terser@5.39.1)(tsx@4.20.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.0.10 diff --git a/server/dev.env b/server/dev.env new file mode 100644 index 0000000..547fe45 --- /dev/null +++ b/server/dev.env @@ -0,0 +1,5 @@ +LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth" +MONGO_URI="op://Environment Variables - Naomi/Hikari/mongo_uri" +DISCORD_TOKEN="op://Environment Variables - Naomi/Hikari/discord_token" +FORUM_API_KEY="op://Environment Variables - Naomi/Hikari/discourse_key" +ANNOUNCEMENT_TOKEN="op://Environment Variables - Naomi/Hikari/announcement_token" \ No newline at end of file diff --git a/server/eslint.config.js b/server/eslint.config.js index abc76e5..7276928 100644 --- a/server/eslint.config.js +++ b/server/eslint.config.js @@ -1,5 +1,18 @@ import NaomisConfig from '@nhcarrigan/eslint-config'; export default [ - ...NaomisConfig + ...NaomisConfig, + { + files: ["src/routes/*.ts"], + rules: { + "max-lines-per-function": "off", + } + }, + { + files: ["src/routes/*.ts"], + rules: { + // We turn this off so we can use the async plugin syntax without needing to await. + "@typescript-eslint/require-await": "off", + } + } ] \ No newline at end of file diff --git a/server/package.json b/server/package.json index 7d8521b..87d935a 100644 --- a/server/package.json +++ b/server/package.json @@ -6,6 +6,7 @@ "type": "module", "scripts": { "lint": "eslint ./src --max-warnings 0", + "dev": "op run --env-file=./dev.env -- tsx watch ./src/index.ts", "build": "tsc", "start": "op run --env-file=./prod.env -- node ./prod/index.js", "test": "echo 'No tests yet' && exit 0" @@ -16,9 +17,12 @@ "packageManager": "pnpm@10.12.3", "dependencies": { "@nhcarrigan/logger": "1.0.0", + "@prisma/client": "6.11.1", "fastify": "5.4.0" }, "devDependencies": { - "@types/node": "24.0.10" + "@types/node": "24.0.10", + "prisma": "6.11.1", + "tsx": "4.20.3" } } diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma new file mode 100644 index 0000000..5044a32 --- /dev/null +++ b/server/prisma/schema.prisma @@ -0,0 +1,19 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mongodb" + url = env("MONGO_URI") +} + +model Announcements { + id String @id @default(auto()) @map("_id") @db.ObjectId + title String + content String + type String + createdAt DateTime @default(now()) @unique +} \ No newline at end of file diff --git a/server/prod.env b/server/prod.env index 12d0e13..547fe45 100644 --- a/server/prod.env +++ b/server/prod.env @@ -1 +1,5 @@ LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth" +MONGO_URI="op://Environment Variables - Naomi/Hikari/mongo_uri" +DISCORD_TOKEN="op://Environment Variables - Naomi/Hikari/discord_token" +FORUM_API_KEY="op://Environment Variables - Naomi/Hikari/discourse_key" +ANNOUNCEMENT_TOKEN="op://Environment Variables - Naomi/Hikari/announcement_token" \ No newline at end of file diff --git a/server/src/config/routesWithoutCors.ts b/server/src/config/routesWithoutCors.ts new file mode 100644 index 0000000..e8bdd1b --- /dev/null +++ b/server/src/config/routesWithoutCors.ts @@ -0,0 +1,15 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +/** + * If you want a route to allow any origin for CORS, add + * the full path to this array. + */ +export const routesWithoutCors = [ + "/", + "/announcement", + "/health", +]; diff --git a/server/src/db/database.ts b/server/src/db/database.ts new file mode 100644 index 0000000..a8c6b55 --- /dev/null +++ b/server/src/db/database.ts @@ -0,0 +1,24 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { PrismaClient } from "@prisma/client"; + +class Database { + private readonly instance: PrismaClient; + + public constructor() { + this.instance = new PrismaClient(); + void this.instance.$connect(); + } + + public getInstance(): PrismaClient { + return this.instance; + } +} + +const database = new Database(); + +export { database }; diff --git a/server/src/hooks/cors.ts b/server/src/hooks/cors.ts new file mode 100644 index 0000000..7b0b668 --- /dev/null +++ b/server/src/hooks/cors.ts @@ -0,0 +1,28 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import type { onRequestHookHandler } from "fastify"; + +/** + * Ensures that form submissions only come from our web application. + * @param request - The request payload from the server. + * @param response - The reply handler from Fastify. + * @returns A Fastify reply if the request is invalid, otherwise undefined. + */ +// eslint-disable-next-line @typescript-eslint/no-misused-promises -- For reasons I cannot comprehend, Fastify seems to require us to return a request? +export const corsHook: onRequestHookHandler = async(request, response) => { + if (!request.url.startsWith("/submit")) { + return undefined; + } + if (request.headers.origin !== "https://forms.nhcarrigan.com") { + return await response. + status(403). + send({ + error: "Forms can only be submitted through our website. Thanks.", + }); + } + return undefined; +}; diff --git a/server/src/index.ts b/server/src/index.ts index c64d8f3..0eee3a0 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -5,19 +5,19 @@ */ import fastify from "fastify"; +import { corsHook } from "./hooks/cors.js"; +import { announcementRoutes } from "./routes/announcement.js"; +import { baseRoutes } from "./routes/base.js"; import { logger } from "./utils/logger.js"; const server = fastify({ logger: false, }); -server.get("/", async(_request, reply) => { - reply.redirect("https://hikari.nhcarrigan.com"); -}); +server.addHook("preHandler", corsHook); -server.get("/health", async(_request, reply) => { - reply.status(200).send("OK~!"); -}); +server.register(baseRoutes); +server.register(announcementRoutes); server.listen({ port: 20_000 }, (error) => { if (error) { diff --git a/server/src/modules/announceOnDiscord.ts b/server/src/modules/announceOnDiscord.ts new file mode 100644 index 0000000..0f6afa0 --- /dev/null +++ b/server/src/modules/announceOnDiscord.ts @@ -0,0 +1,65 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ +/* eslint-disable @typescript-eslint/naming-convention -- we are making raw API calls. */ + +const channelIds = { + community: "1386105484313886820", + products: "1386105452881776661", +} as const; +const roleIds = { + community: "1386107941224054895", + products: "1386107909699666121", +} as const; + +/** + * Forwards an announcement to our Discord server. + * @param title - The title of the announcement. + * @param content - The main body of the announcement. + * @param type - Whether the announcement is for a product or community. + * @returns A message indicating the success or failure of the operation. + */ +export const announceOnDiscord = async( + title: string, + content: string, + type: "products" | "community", +): Promise => { + const messageRequest = await fetch( + `https://discord.com/api/v10/channels/${channelIds[type]}/messages`, + { + body: JSON.stringify({ + allowed_mentions: { parse: [ "users", "roles" ] }, + content: `# ${title}\n\n${content}\n-# <@&${roleIds[type]}>`, + }), + headers: { + "Authorization": `Bot ${process.env.DISCORD_TOKEN ?? ""}`, + "Content-Type": "application/json", + }, + method: "POST", + }, + ); + if (messageRequest.status !== 200) { + return "Failed to send message to Discord."; + } + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- fetch does not accept generics. + const message = await messageRequest.json() as { id?: string }; + if (message.id === undefined) { + return "Failed to parse message ID, cannot crosspost."; + } + const crosspostRequest = await fetch( + `https://discord.com/api/v10/channels/${channelIds[type]}/messages/${message.id}/crosspost`, + { + headers: { + "Authorization": `Bot ${process.env.DISCORD_TOKEN ?? ""}`, + "Content-Type": "application/json", + }, + method: "POST", + }, + ); + if (!crosspostRequest.ok) { + return "Failed to crosspost message to Discord."; + } + return "Successfully sent and published message to Discord."; +}; diff --git a/server/src/modules/announceOnForum.ts b/server/src/modules/announceOnForum.ts new file mode 100644 index 0000000..a4a5b9a --- /dev/null +++ b/server/src/modules/announceOnForum.ts @@ -0,0 +1,40 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ +/* eslint-disable @typescript-eslint/naming-convention -- we are making raw API calls. */ +/** + * Forwards an announcement to our Discord server. + * @param title - The title of the announcement. + * @param content - The main body of the announcement. + * @param type - Whether the announcement is for a product or community. + * @returns A message indicating the success or failure of the operation. + */ +export const announceOnForum = async( + title: string, + content: string, + type: "products" | "community", +): Promise => { + const forumRequest = await fetch( + `https://forum.nhcarrigan.com/posts.json`, + { + body: JSON.stringify({ + category: 14, + raw: content, + tags: [ type ], + title: title, + }), + headers: { + "Api-Key": process.env.FORUM_API_KEY ?? "", + "Api-Username": "Hikari", + "Content-Type": "application/json", + }, + method: "POST", + }, + ); + if (forumRequest.status !== 200) { + return "Failed to send message to forum."; + } + return "Successfully sent message to forum."; +}; diff --git a/server/src/routes/announcement.ts b/server/src/routes/announcement.ts new file mode 100644 index 0000000..1b20680 --- /dev/null +++ b/server/src/routes/announcement.ts @@ -0,0 +1,95 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { database } from "../db/database.js"; +import { announceOnDiscord } from "../modules/announceOnDiscord.js"; +import { announceOnForum } from "../modules/announceOnForum.js"; +import type { FastifyPluginAsync } from "fastify"; + +/** + * Mounts the entry routes for the application. These routes + * should not require CORS, as they are used by external services + * such as our uptime monitor. + * @param server - The Fastify server instance. + */ +export const announcementRoutes: FastifyPluginAsync = async(server) => { + server.get("/announcements", async(_request, reply) => { + const announcements = await database.getInstance().announcements.findMany({ + orderBy: { + createdAt: "desc", + }, + take: 10, + }); + return await reply.status(200).type("application/json"). + send(announcements); + }); + + // eslint-disable-next-line @typescript-eslint/naming-convention -- Fastify requires Body instead of body. + server.post<{ Body: { title: string; content: string; type: string } }>( + "/announcement", + // eslint-disable-next-line complexity -- This is a complex route, but it is necessary to validate the announcement. + async(request, reply) => { + const token = request.headers.authorization; + if (token === undefined || token !== process.env.ANNOUNCEMENT_TOKEN) { + return await reply.status(401).send({ + error: + // eslint-disable-next-line stylistic/max-len -- Big boi string. + "This endpoint requires a special auth token. If you believe you should have access, please contact Naomi.", + }); + } + + const { title, content, type } = request.body; + if ( + typeof title !== "string" + || typeof content !== "string" + || typeof type !== "string" + || title.length === 0 + || content.length === 0 + || type.length === 0 + ) { + return await reply.status(400).send({ + error: "Missing required fields.", + }); + } + + if (title.length < 20) { + return await reply.status(400).send({ + error: + // eslint-disable-next-line stylistic/max-len -- Big boi string. + "Title must be at least 20 characters long so that it may be posted on our forum.", + }); + } + + if (content.length < 50) { + return await reply.status(400).send({ + error: + // eslint-disable-next-line stylistic/max-len -- Big boi string. + "Content must be at least 50 characters long so that it may be posted on our forum.", + }); + } + + if (type !== "products" && type !== "community") { + return await reply.status(400).send({ + error: "Invalid announcement type.", + }); + } + + await database.getInstance().announcements.create({ + data: { + content, + title, + type, + }, + }); + + const discord = await announceOnDiscord(title, content, type); + const forum = await announceOnForum(title, content, type); + return await reply.status(201).send({ + message: `Announcement processed. Discord: ${discord}, Forum: ${forum}`, + }); + }, + ); +}; diff --git a/server/src/routes/base.ts b/server/src/routes/base.ts new file mode 100644 index 0000000..43efe75 --- /dev/null +++ b/server/src/routes/base.ts @@ -0,0 +1,23 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import type { FastifyPluginAsync } from "fastify"; + +/** + * Mounts the entry routes for the application. These routes + * should not require CORS, as they are used by external services + * such as our uptime monitor. + * @param server - The Fastify server instance. + */ +export const baseRoutes: FastifyPluginAsync = async(server) => { + server.get("/", async(_request, reply) => { + return await reply.redirect("https://hikari.nhcarrigan.com"); + }); + + server.get("/health", async(_request, reply) => { + return await reply.status(200).send("OK~!"); + }); +};