fix: tech debt batch 1 — TS errors, vulnerabilities, dead code
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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: [
|
||||
|
||||
263
package-lock.json
generated
263
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -933,12 +933,14 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
|
||||
// Group files by round
|
||||
type FileItem = (typeof project.files)[number]
|
||||
const roundMap = new Map<string, { roundId: string | null; roundName: string; sortOrder: number; files: FileItem[] }>()
|
||||
// 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: [],
|
||||
|
||||
@@ -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<boolean> {
|
||||
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<void> {
|
||||
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}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<SessionResult> {
|
||||
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',
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user