From 1356809cb1b34aa4f67089b6d1c0b39b357a8070 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 7 Mar 2026 23:51:44 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20tech=20debt=20batch=201=20=E2=80=94=20TS?= =?UTF-8?q?=20errors,=20vulnerabilities,=20dead=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed 12 TypeScript errors across analytics.ts, observer-project-detail.tsx, bulk-upload/page.tsx, settings/profile/page.tsx - npm audit: 8 vulnerabilities resolved (1 critical, 4 high, 3 moderate) - Deleted 3 dead files: live-control.ts (618 lines), feature-flags.ts, file-type-categories.ts - Removed typescript.ignoreBuildErrors: true — TS errors now block builds Co-Authored-By: Claude Opus 4.6 --- next.config.ts | 3 +- package-lock.json | 263 ++++---- .../admin/projects/bulk-upload/page.tsx | 1 + src/app/(settings)/settings/profile/page.tsx | 16 +- .../observer/observer-project-detail.tsx | 8 +- src/lib/feature-flags.ts | 49 -- src/lib/file-type-categories.ts | 30 - src/server/routers/analytics.ts | 3 +- src/server/services/live-control.ts | 618 ------------------ 9 files changed, 149 insertions(+), 842 deletions(-) delete mode 100644 src/lib/feature-flags.ts delete mode 100644 src/lib/file-type-categories.ts delete mode 100644 src/server/services/live-control.ts diff --git a/next.config.ts b/next.config.ts index 3475016..092727d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,8 +4,7 @@ const nextConfig: NextConfig = { output: 'standalone', serverExternalPackages: ['@prisma/client', 'minio'], typescript: { - // We run tsc --noEmit separately before each push - ignoreBuildErrors: true, + ignoreBuildErrors: false, }, experimental: { optimizePackageImports: [ diff --git a/package-lock.json b/package-lock.json index 69e2bd4..a8faf4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3830,9 +3830,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", - "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -3844,9 +3844,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", - "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -3858,9 +3858,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", - "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -3872,9 +3872,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", - "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -3886,9 +3886,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", - "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -3900,9 +3900,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", - "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -3914,9 +3914,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", - "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -3928,9 +3928,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", - "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -3942,9 +3942,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", - "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -3956,9 +3956,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", - "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -3970,9 +3970,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", - "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], @@ -3984,9 +3984,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", - "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -3998,9 +3998,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", - "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], @@ -4012,9 +4012,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", - "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -4026,9 +4026,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", - "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -4040,9 +4040,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", - "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -4054,9 +4054,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", - "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -4068,9 +4068,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", - "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -4082,9 +4082,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", - "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -4096,9 +4096,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", - "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], @@ -4110,9 +4110,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", - "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -4124,9 +4124,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", - "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -4138,9 +4138,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", - "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -4152,9 +4152,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", - "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -4166,9 +4166,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", - "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -5426,13 +5426,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5967,9 +5967,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -7504,11 +7504,14 @@ } }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optional": true, + "engines": { + "node": ">=20" + }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -8603,9 +8606,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.4.tgz", + "integrity": "sha512-jE8ugADnYOBsu1uaoayVl1tVKAMNOXyjwvv2U6udEA2ORBhDooJDWoGxTkhd4Qn4yh59JVVt/pKXtjPwx9OguQ==", "funding": [ { "type": "github", @@ -8614,7 +8617,7 @@ ], "license": "MIT", "dependencies": { - "strnum": "^1.1.1" + "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" @@ -10325,12 +10328,12 @@ "license": "MIT" }, "node_modules/jspdf": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.1.0.tgz", - "integrity": "sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.0.tgz", + "integrity": "sha512-hR/hnRevAXXlrjeqU5oahOE+Ln9ORJUB5brLHHqH67A+RBQZuFr5GkbI9XQI8OUFSEezKegsi45QRpc4bGj75Q==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.28.6", "fast-png": "^6.2.0", "fflate": "^0.8.1" }, @@ -10936,9 +10939,9 @@ } }, "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1", @@ -11877,9 +11880,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -14149,9 +14152,9 @@ } }, "node_modules/rollup": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", - "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -14165,31 +14168,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.0", - "@rollup/rollup-android-arm64": "4.57.0", - "@rollup/rollup-darwin-arm64": "4.57.0", - "@rollup/rollup-darwin-x64": "4.57.0", - "@rollup/rollup-freebsd-arm64": "4.57.0", - "@rollup/rollup-freebsd-x64": "4.57.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", - "@rollup/rollup-linux-arm-musleabihf": "4.57.0", - "@rollup/rollup-linux-arm64-gnu": "4.57.0", - "@rollup/rollup-linux-arm64-musl": "4.57.0", - "@rollup/rollup-linux-loong64-gnu": "4.57.0", - "@rollup/rollup-linux-loong64-musl": "4.57.0", - "@rollup/rollup-linux-ppc64-gnu": "4.57.0", - "@rollup/rollup-linux-ppc64-musl": "4.57.0", - "@rollup/rollup-linux-riscv64-gnu": "4.57.0", - "@rollup/rollup-linux-riscv64-musl": "4.57.0", - "@rollup/rollup-linux-s390x-gnu": "4.57.0", - "@rollup/rollup-linux-x64-gnu": "4.57.0", - "@rollup/rollup-linux-x64-musl": "4.57.0", - "@rollup/rollup-openbsd-x64": "4.57.0", - "@rollup/rollup-openharmony-arm64": "4.57.0", - "@rollup/rollup-win32-arm64-msvc": "4.57.0", - "@rollup/rollup-win32-ia32-msvc": "4.57.0", - "@rollup/rollup-win32-x64-gnu": "4.57.0", - "@rollup/rollup-win32-x64-msvc": "4.57.0", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -15637,9 +15640,9 @@ } }, "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", "license": "MIT" }, "node_modules/undici-types": { diff --git a/src/app/(admin)/admin/projects/bulk-upload/page.tsx b/src/app/(admin)/admin/projects/bulk-upload/page.tsx index 6db56a3..f7e6d76 100644 --- a/src/app/(admin)/admin/projects/bulk-upload/page.tsx +++ b/src/app/(admin)/admin/projects/bulk-upload/page.tsx @@ -1,6 +1,7 @@ 'use client' import { useState, useCallback, useRef, useEffect, useMemo } from 'react' +import Link from 'next/link' import { useRouter } from 'next/navigation' import { trpc } from '@/lib/trpc/client' import { toast } from 'sonner' diff --git a/src/app/(settings)/settings/profile/page.tsx b/src/app/(settings)/settings/profile/page.tsx index fde905f..b189e04 100644 --- a/src/app/(settings)/settings/profile/page.tsx +++ b/src/app/(settings)/settings/profile/page.tsx @@ -94,10 +94,10 @@ export default function ProfileSettingsPage() { setExpertiseTags(user.expertiseTags || []) setDigestFrequency(user.digestFrequency || 'none') setPreferredWorkload(user.preferredWorkload ?? null) - const avail = user.availabilityJson as { startDate?: string; endDate?: string } | null - if (avail) { - setAvailabilityStart(avail.startDate || '') - setAvailabilityEnd(avail.endDate || '') + const avail = user.availabilityJson as Array<{ start?: string; end?: string }> | null + if (avail && avail.length > 0) { + setAvailabilityStart(avail[0].start || '') + setAvailabilityEnd(avail[0].end || '') } setProfileLoaded(true) } @@ -114,10 +114,10 @@ export default function ProfileSettingsPage() { expertiseTags, digestFrequency: digestFrequency as 'none' | 'daily' | 'weekly', preferredWorkload: preferredWorkload ?? undefined, - availabilityJson: (availabilityStart || availabilityEnd) ? { - startDate: availabilityStart || undefined, - endDate: availabilityEnd || undefined, - } : undefined, + availabilityJson: (availabilityStart || availabilityEnd) ? [{ + start: availabilityStart || '', + end: availabilityEnd || '', + }] : undefined, }) toast.success('Profile updated successfully') refetch() diff --git a/src/components/observer/observer-project-detail.tsx b/src/components/observer/observer-project-detail.tsx index ded9c8e..248534c 100644 --- a/src/components/observer/observer-project-detail.tsx +++ b/src/components/observer/observer-project-detail.tsx @@ -933,12 +933,14 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) { // Group files by round type FileItem = (typeof project.files)[number] const roundMap = new Map() + // Build roundId→round lookup from competitionRounds + const roundLookup = new Map(competitionRounds.map((r, idx) => [r.id, { name: r.name, sortOrder: idx }])) for (const f of project.files) { - const key = (f as any).roundId ?? '__none__' + const key = f.roundId ?? '__none__' if (!roundMap.has(key)) { - const round = (f as any).round as { id: string; name: string; sortOrder: number } | null + const round = f.roundId ? roundLookup.get(f.roundId) : null roundMap.set(key, { - roundId: round?.id ?? null, + roundId: f.roundId ?? null, roundName: round?.name ?? 'Other Files', sortOrder: round?.sortOrder ?? 999, files: [], diff --git a/src/lib/feature-flags.ts b/src/lib/feature-flags.ts deleted file mode 100644 index d163adb..0000000 --- a/src/lib/feature-flags.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { prisma } from '@/lib/prisma' - -/** - * Feature flag keys — used to control progressive rollout of new architecture. - * Stored as SystemSetting records with category FEATURE_FLAGS. - */ -export const FEATURE_FLAGS = { - /** Use Competition/Round model instead of Pipeline/Track/Stage */ - USE_COMPETITION_MODEL: 'feature.useCompetitionModel', -} as const - -type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS] - -/** - * Check if a feature flag is enabled (server-side). - * Returns false if the flag doesn't exist in the database. - */ -export async function isFeatureEnabled(flag: FeatureFlagKey): Promise { - try { - const setting = await prisma.systemSettings.findUnique({ - where: { key: flag }, - }) - // Default to true for competition model (legacy Pipeline system removed) - if (!setting) return flag === FEATURE_FLAGS.USE_COMPETITION_MODEL ? true : false - return setting.value === 'true' - } catch { - return flag === FEATURE_FLAGS.USE_COMPETITION_MODEL ? true : false - } -} - -/** - * Set a feature flag value (server-side, admin only). - */ -export async function setFeatureFlag( - flag: FeatureFlagKey, - enabled: boolean, -): Promise { - await prisma.systemSettings.upsert({ - where: { key: flag }, - update: { value: String(enabled) }, - create: { - key: flag, - value: String(enabled), - type: 'BOOLEAN', - category: 'FEATURE_FLAGS', - description: `Feature flag: ${flag}`, - }, - }) -} diff --git a/src/lib/file-type-categories.ts b/src/lib/file-type-categories.ts deleted file mode 100644 index 35fe236..0000000 --- a/src/lib/file-type-categories.ts +++ /dev/null @@ -1,30 +0,0 @@ -export type FileTypeCategory = { - id: string - label: string - mimeTypes: string[] - extensions: string[] -} - -export const FILE_TYPE_CATEGORIES: FileTypeCategory[] = [ - { id: 'pdf', label: 'PDF', mimeTypes: ['application/pdf'], extensions: ['.pdf'] }, - { id: 'word', label: 'Word', mimeTypes: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], extensions: ['.doc', '.docx'] }, - { id: 'powerpoint', label: 'PowerPoint', mimeTypes: ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], extensions: ['.ppt', '.pptx'] }, - { id: 'excel', label: 'Excel', mimeTypes: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], extensions: ['.xls', '.xlsx'] }, - { id: 'images', label: 'Images', mimeTypes: ['image/*'], extensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'] }, - { id: 'videos', label: 'Videos', mimeTypes: ['video/*'], extensions: ['.mp4', '.mov', '.avi', '.webm'] }, -] - -/** Get active category IDs from a list of mime types */ -export function getActiveCategoriesFromMimeTypes(mimeTypes: string[]): string[] { - if (!mimeTypes || !Array.isArray(mimeTypes)) return [] - return FILE_TYPE_CATEGORIES.filter((cat) => - cat.mimeTypes.some((mime) => mimeTypes.includes(mime)) - ).map((cat) => cat.id) -} - -/** Convert category IDs to flat mime type array */ -export function categoriesToMimeTypes(categoryIds: string[]): string[] { - return FILE_TYPE_CATEGORIES.filter((cat) => categoryIds.includes(cat.id)).flatMap( - (cat) => cat.mimeTypes - ) -} diff --git a/src/server/routers/analytics.ts b/src/server/routers/analytics.ts index 498246c..a901500 100644 --- a/src/server/routers/analytics.ts +++ b/src/server/routers/analytics.ts @@ -1379,11 +1379,10 @@ export const analyticsRouter = router({ bucket: true, objectKey: true, pageCount: true, textPreview: true, detectedLang: true, langConfidence: true, analyzedAt: true, roundId: true, - round: { select: { id: true, name: true, roundType: true, sortOrder: true } }, requirementId: true, requirement: { select: { id: true, name: true, description: true, isRequired: true } }, }, - orderBy: [{ round: { sortOrder: 'asc' } }, { createdAt: 'asc' }], + orderBy: [{ createdAt: 'asc' }], }, teamMembers: { include: { diff --git a/src/server/services/live-control.ts b/src/server/services/live-control.ts deleted file mode 100644 index 51b707a..0000000 --- a/src/server/services/live-control.ts +++ /dev/null @@ -1,618 +0,0 @@ -/** - * Live Control Service - * - * Manages real-time control of live final events within a round. - * Handles session management, project cursor navigation, queue reordering, - * pause/resume, and cohort voting windows. - * - * The LiveProgressCursor tracks the current position in a live presentation - * sequence. Cohorts group projects for voting with configurable windows. - */ - -import type { PrismaClient } from '@prisma/client' -import { logAudit } from '@/server/utils/audit' - -// ─── Types ────────────────────────────────────────────────────────────────── - -export interface SessionResult { - success: boolean - sessionId: string | null - cursorId: string | null - errors?: string[] -} - -export interface CursorState { - roundId: string - sessionId: string - activeProjectId: string | null - activeOrderIndex: number - isPaused: boolean - totalProjects: number -} - -// ─── Helpers ──────────────────────────────────────────────────────────────── - -function generateSessionId(): string { - const timestamp = Date.now().toString(36) - const random = Math.random().toString(36).substring(2, 8) - return `live-${timestamp}-${random}` -} - -// ─── Start Session ────────────────────────────────────────────────────────── - -/** - * Create or reset a LiveProgressCursor for a round. If a cursor already exists, - * it is reset to the beginning. A new sessionId is always generated. - */ -export async function startSession( - roundId: string, - actorId: string, - prisma: PrismaClient | any -): Promise { - try { - // Verify round exists and is a LIVE_FINAL type - const round = await prisma.round.findUnique({ - where: { id: roundId }, - }) - - if (!round) { - return { - success: false, - sessionId: null, - cursorId: null, - errors: [`Round ${roundId} not found`], - } - } - - if (round.roundType !== 'LIVE_FINAL') { - return { - success: false, - sessionId: null, - cursorId: null, - errors: [ - `Round "${round.name}" is type ${round.roundType}, expected LIVE_FINAL`, - ], - } - } - - // Find the first project in the first cohort - const firstCohortProject = await prisma.cohortProject.findFirst({ - where: { - cohort: { roundId }, - }, - orderBy: { sortOrder: 'asc' as const }, - select: { projectId: true }, - }) - - const sessionId = generateSessionId() - - // Upsert the cursor (one per round) - const cursor = await prisma.liveProgressCursor.upsert({ - where: { roundId }, - create: { - roundId, - sessionId, - activeProjectId: firstCohortProject?.projectId ?? null, - activeOrderIndex: 0, - isPaused: false, - }, - update: { - sessionId, - activeProjectId: firstCohortProject?.projectId ?? null, - activeOrderIndex: 0, - isPaused: false, - }, - }) - - // Decision audit log - await prisma.decisionAuditLog.create({ - data: { - eventType: 'live.session_started', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - actorId, - detailsJson: { - roundId, - sessionId, - firstProjectId: firstCohortProject?.projectId ?? null, - }, - }, - }) - - await logAudit({ - prisma, - userId: actorId, - action: 'LIVE_SESSION_STARTED', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - detailsJson: { roundId, sessionId }, - }) - - return { - success: true, - sessionId, - cursorId: cursor.id, - } - } catch (error) { - console.error('[LiveControl] Failed to start session:', error) - return { - success: false, - sessionId: null, - cursorId: null, - errors: [ - error instanceof Error ? error.message : 'Failed to start live session', - ], - } - } -} - -// ─── Set Active Project ───────────────────────────────────────────────────── - -/** - * Set the currently active project in the live session. - * Validates that the project belongs to a cohort in this round and performs - * a version check on the cursor's sessionId to prevent stale updates. - */ -export async function setActiveProject( - roundId: string, - projectId: string, - actorId: string, - prisma: PrismaClient | any -): Promise<{ success: boolean; errors?: string[] }> { - try { - // Verify cursor exists - const cursor = await prisma.liveProgressCursor.findUnique({ - where: { roundId }, - }) - - if (!cursor) { - return { - success: false, - errors: ['No live session found for this round. Start a session first.'], - } - } - - // Verify project is in a cohort for this round - const cohortProject = await prisma.cohortProject.findFirst({ - where: { - projectId, - cohort: { roundId }, - }, - select: { id: true, sortOrder: true }, - }) - - if (!cohortProject) { - return { - success: false, - errors: [ - `Project ${projectId} is not in any cohort for round ${roundId}`, - ], - } - } - - // Update cursor - await prisma.liveProgressCursor.update({ - where: { roundId }, - data: { - activeProjectId: projectId, - activeOrderIndex: cohortProject.sortOrder, - }, - }) - - // Audit - await prisma.decisionAuditLog.create({ - data: { - eventType: 'live.cursor_updated', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - actorId, - detailsJson: { - roundId, - projectId, - orderIndex: cohortProject.sortOrder, - action: 'setActiveProject', - }, - }, - }) - - await logAudit({ - prisma, - userId: actorId, - action: 'LIVE_SET_ACTIVE_PROJECT', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - detailsJson: { projectId, orderIndex: cohortProject.sortOrder }, - }) - - return { success: true } - } catch (error) { - console.error('[LiveControl] Failed to set active project:', error) - return { - success: false, - errors: [ - error instanceof Error - ? error.message - : 'Failed to set active project', - ], - } - } -} - -// ─── Jump to Project ──────────────────────────────────────────────────────── - -/** - * Jump to a project by its order index in the cohort queue. - */ -export async function jumpToProject( - roundId: string, - orderIndex: number, - actorId: string, - prisma: PrismaClient | any -): Promise<{ success: boolean; projectId?: string; errors?: string[] }> { - try { - const cursor = await prisma.liveProgressCursor.findUnique({ - where: { roundId }, - }) - - if (!cursor) { - return { - success: false, - errors: ['No live session found for this round'], - } - } - - // Find the CohortProject at the given sort order - const cohortProject = await prisma.cohortProject.findFirst({ - where: { - cohort: { roundId }, - sortOrder: orderIndex, - }, - select: { projectId: true, sortOrder: true }, - }) - - if (!cohortProject) { - return { - success: false, - errors: [`No project found at order index ${orderIndex}`], - } - } - - // Update cursor - await prisma.liveProgressCursor.update({ - where: { roundId }, - data: { - activeProjectId: cohortProject.projectId, - activeOrderIndex: orderIndex, - }, - }) - - await prisma.decisionAuditLog.create({ - data: { - eventType: 'live.cursor_updated', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - actorId, - detailsJson: { - roundId, - projectId: cohortProject.projectId, - orderIndex, - action: 'jumpToProject', - }, - }, - }) - - await logAudit({ - prisma, - userId: actorId, - action: 'LIVE_JUMP_TO_PROJECT', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - detailsJson: { orderIndex, projectId: cohortProject.projectId }, - }) - - return { success: true, projectId: cohortProject.projectId } - } catch (error) { - console.error('[LiveControl] Failed to jump to project:', error) - return { - success: false, - errors: [ - error instanceof Error ? error.message : 'Failed to jump to project', - ], - } - } -} - -// ─── Reorder Queue ────────────────────────────────────────────────────────── - -/** - * Reorder the presentation queue by updating CohortProject sortOrder values. - * newOrder is an array of cohortProjectIds in the desired order. - */ -export async function reorderQueue( - roundId: string, - newOrder: string[], - actorId: string, - prisma: PrismaClient | any -): Promise<{ success: boolean; errors?: string[] }> { - try { - // Verify all provided IDs belong to cohorts in this round - const cohortProjects = await prisma.cohortProject.findMany({ - where: { - id: { in: newOrder }, - cohort: { roundId }, - }, - select: { id: true }, - }) - - const validIds = new Set(cohortProjects.map((cp: any) => cp.id)) - const invalidIds = newOrder.filter((id) => !validIds.has(id)) - - if (invalidIds.length > 0) { - return { - success: false, - errors: [ - `CohortProject IDs not found in round ${roundId}: ${invalidIds.join(', ')}`, - ], - } - } - - // Update sortOrder for each item - await prisma.$transaction( - newOrder.map((cohortProjectId, index) => - prisma.cohortProject.update({ - where: { id: cohortProjectId }, - data: { sortOrder: index }, - }) - ) - ) - - const cursor = await prisma.liveProgressCursor.findUnique({ - where: { roundId }, - }) - - if (cursor) { - await prisma.decisionAuditLog.create({ - data: { - eventType: 'live.queue_reordered', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - actorId, - detailsJson: { - roundId, - newOrderCount: newOrder.length, - }, - }, - }) - } - - await logAudit({ - prisma, - userId: actorId, - action: 'LIVE_REORDER_QUEUE', - entityType: 'Round', - entityId: roundId, - detailsJson: { reorderedCount: newOrder.length }, - }) - - return { success: true } - } catch (error) { - console.error('[LiveControl] Failed to reorder queue:', error) - return { - success: false, - errors: [ - error instanceof Error ? error.message : 'Failed to reorder queue', - ], - } - } -} - -// ─── Pause / Resume ───────────────────────────────────────────────────────── - -/** - * Toggle the pause state of a live session. - */ -export async function pauseResume( - roundId: string, - isPaused: boolean, - actorId: string, - prisma: PrismaClient | any -): Promise<{ success: boolean; errors?: string[] }> { - try { - const cursor = await prisma.liveProgressCursor.findUnique({ - where: { roundId }, - }) - - if (!cursor) { - return { - success: false, - errors: ['No live session found for this round'], - } - } - - await prisma.liveProgressCursor.update({ - where: { roundId }, - data: { isPaused }, - }) - - await prisma.decisionAuditLog.create({ - data: { - eventType: isPaused ? 'live.session_paused' : 'live.session_resumed', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - actorId, - detailsJson: { - roundId, - isPaused, - sessionId: cursor.sessionId, - }, - }, - }) - - await logAudit({ - prisma, - userId: actorId, - action: isPaused ? 'LIVE_SESSION_PAUSED' : 'LIVE_SESSION_RESUMED', - entityType: 'LiveProgressCursor', - entityId: cursor.id, - detailsJson: { roundId, isPaused }, - }) - - return { success: true } - } catch (error) { - console.error('[LiveControl] Failed to pause/resume:', error) - return { - success: false, - errors: [ - error instanceof Error - ? error.message - : 'Failed to toggle pause state', - ], - } - } -} - -// ─── Cohort Window Management ─────────────────────────────────────────────── - -/** - * Open a cohort's voting window. Sets isOpen to true and records the - * window open timestamp. - */ -export async function openCohortWindow( - cohortId: string, - actorId: string, - prisma: PrismaClient | any -): Promise<{ success: boolean; errors?: string[] }> { - try { - const cohort = await prisma.cohort.findUnique({ - where: { id: cohortId }, - }) - - if (!cohort) { - return { success: false, errors: [`Cohort ${cohortId} not found`] } - } - - if (cohort.isOpen) { - return { - success: false, - errors: [`Cohort "${cohort.name}" is already open`], - } - } - - const now = new Date() - - await prisma.cohort.update({ - where: { id: cohortId }, - data: { - isOpen: true, - windowOpenAt: now, - }, - }) - - await prisma.decisionAuditLog.create({ - data: { - eventType: 'live.cohort_opened', - entityType: 'Cohort', - entityId: cohortId, - actorId, - detailsJson: { - cohortName: cohort.name, - roundId: cohort.roundId, - openedAt: now.toISOString(), - }, - }, - }) - - await logAudit({ - prisma, - userId: actorId, - action: 'LIVE_COHORT_OPENED', - entityType: 'Cohort', - entityId: cohortId, - detailsJson: { cohortName: cohort.name, roundId: cohort.roundId }, - }) - - return { success: true } - } catch (error) { - console.error('[LiveControl] Failed to open cohort window:', error) - return { - success: false, - errors: [ - error instanceof Error - ? error.message - : 'Failed to open cohort window', - ], - } - } -} - -/** - * Close a cohort's voting window. Sets isOpen to false and records the - * window close timestamp. - */ -export async function closeCohortWindow( - cohortId: string, - actorId: string, - prisma: PrismaClient | any -): Promise<{ success: boolean; errors?: string[] }> { - try { - const cohort = await prisma.cohort.findUnique({ - where: { id: cohortId }, - }) - - if (!cohort) { - return { success: false, errors: [`Cohort ${cohortId} not found`] } - } - - if (!cohort.isOpen) { - return { - success: false, - errors: [`Cohort "${cohort.name}" is already closed`], - } - } - - const now = new Date() - - await prisma.cohort.update({ - where: { id: cohortId }, - data: { - isOpen: false, - windowCloseAt: now, - }, - }) - - await prisma.decisionAuditLog.create({ - data: { - eventType: 'live.cohort_closed', - entityType: 'Cohort', - entityId: cohortId, - actorId, - detailsJson: { - cohortName: cohort.name, - roundId: cohort.roundId, - closedAt: now.toISOString(), - }, - }, - }) - - await logAudit({ - prisma, - userId: actorId, - action: 'LIVE_COHORT_CLOSED', - entityType: 'Cohort', - entityId: cohortId, - detailsJson: { cohortName: cohort.name, roundId: cohort.roundId }, - }) - - return { success: true } - } catch (error) { - console.error('[LiveControl] Failed to close cohort window:', error) - return { - success: false, - errors: [ - error instanceof Error - ? error.message - : 'Failed to close cohort window', - ], - } - } -}