From cdfa8022add8c4483a3e09e0711ec74c2f91cf43 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Mon, 17 Aug 2026 12:13:28 -0400 Subject: [PATCH 1/7] Skeleton of project getAll endpoint --- packages/admin/package-lock.json | 30 +-- packages/server/package-lock.json | 203 +++++++----------- packages/server/package.json | 3 + packages/server/src/main.ts | 15 ++ .../controllers/project-admin.controller.ts | 25 ++- .../server/src/project/project.service.ts | 15 ++ packages/server/src/shared/ra-query.dto.ts | 36 ++++ 7 files changed, 172 insertions(+), 155 deletions(-) create mode 100644 packages/server/src/shared/ra-query.dto.ts diff --git a/packages/admin/package-lock.json b/packages/admin/package-lock.json index 524f14a6..02287375 100644 --- a/packages/admin/package-lock.json +++ b/packages/admin/package-lock.json @@ -59,7 +59,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -333,7 +332,6 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -377,7 +375,6 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -678,7 +675,6 @@ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.3.1.tgz", "integrity": "sha512-rZj5ccG7vkpV38o/l4ys+chfE9GFypmvZr9dSNBoYVCNBD9yC6KfKq1TYpEMshWbjic7GKQ8MA1LQlcpGgcq9Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.29.7" }, @@ -705,7 +701,6 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.3.1.tgz", "integrity": "sha512-NahAEGIXqS1K0bA4th1jeFxBguS59NOcLbMA0vU+fSaPWKjtwGBGGHeTwlc9PSmzMjOZKceeFWESB8fVHr31hA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.29.7", "@mui/core-downloads-tracker": "^9.3.1", @@ -816,7 +811,6 @@ "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.3.0.tgz", "integrity": "sha512-0l4LqHJxZj65xSrioniGsxm7VNoGXonPo203oZjhBUvIDPeBqRTb7Mqc45Qxs6sO6WGR7WE/9cJ6lb4lkvHkjg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.29.7", "@mui/private-theming": "^9.3.0", @@ -874,7 +868,6 @@ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.3.0.tgz", "integrity": "sha512-2HZdHwWJ6eB+7lVGSOHsByGw8jeRulT4g0NZ608Wb8Q57DE2jbNqrWPFuJsvkQQiBiTmlpvQL3i+/62zsiPrkw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.29.7", "@mui/types": "^9.3.0", @@ -1442,7 +1435,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.101.4" }, @@ -1481,7 +1473,6 @@ "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~8.3.0" } @@ -1503,7 +1494,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1579,7 +1569,6 @@ "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", @@ -1801,7 +1790,6 @@ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1925,7 +1913,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", @@ -2096,8 +2083,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/date-fns": { "version": "3.6.0", @@ -2299,7 +2285,6 @@ "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "packages/*" ], @@ -3518,7 +3503,6 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3631,7 +3615,6 @@ "resolved": "https://registry.npmjs.org/ra-core/-/ra-core-5.15.0.tgz", "integrity": "sha512-8PP+ulxmEPW/p9V4j0kwydD1yLYdApzYcfOVXY6MDezSd/1QzJKdIHO5WrXWThlCMId/nUpmBrtCHLCYYZ9t5A==", "license": "MIT", - "peer": true, "dependencies": { "date-fns": "^3.6.0", "eventemitter3": "^5.0.1", @@ -3721,7 +3704,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3755,7 +3737,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3797,7 +3778,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.85.0.tgz", "integrity": "sha512-U2MTriFXnclmV4rOE20p2DcRFv5WEg3FIcBFOKcOLFHDVvGIMPvLTkTWefUsonmlaVy23khVDxDWym6uJVGOzw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -3826,15 +3806,13 @@ "version": "19.2.8", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-router": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", "license": "MIT", - "peer": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -3857,7 +3835,6 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", "license": "MIT", - "peer": true, "dependencies": { "react-router": "7.18.2" }, @@ -4126,7 +4103,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4213,7 +4189,6 @@ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -4347,7 +4322,6 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json index 0d13d108..07f8bf23 100644 --- a/packages/server/package-lock.json +++ b/packages/server/package-lock.json @@ -12,6 +12,7 @@ "@apollo/server": "^5.5.1", "@apollo/subgraph": "^2.14.3", "@as-integrations/express5": "^1.1.2", + "@bu-sail/ra-query-core": "^0.1.2", "@google-cloud/run": "^4.0.0", "@google-cloud/secret-manager": "^7.0.0", "@google-cloud/storage": "^7.22.0", @@ -31,6 +32,8 @@ "@types/passport-jwt": "^4.0.1", "casbin": "^5.51.1", "casbin-mongoose-adapter": "^5.6.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", "csv-parser": "^3.2.1", "firebase-admin": "^14.2.0", "graphql-request": "^7.4.0", @@ -286,7 +289,6 @@ "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.1.tgz", "integrity": "sha512-Rn3g5TJQsMSUY23CWZTghWdBWyjX7dP1eaEBPkvmM2RHi82cDcpgTIkSCbGvtTUEGjwopLv1AAooU/n7iIZ20A==", "license": "MIT", - "peer": true, "dependencies": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^2.0.0", @@ -361,7 +363,6 @@ "resolved": "https://registry.npmjs.org/@apollo/subgraph/-/subgraph-2.14.3.tgz", "integrity": "sha512-ElfvdJJFKUQn32d7zUkBaqWhco5nSknlZKp2u0Z1FdDb5PGWfXUxuVQRpCv9zfqXXwKfCWithN6uchkDzy+khQ==", "license": "MIT", - "peer": true, "dependencies": { "@apollo/cache-control-types": "^1.0.2", "@apollo/federation-internals": "2.14.3" @@ -597,7 +598,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -1096,6 +1096,12 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@bu-sail/ra-query-core": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@bu-sail/ra-query-core/-/ra-query-core-0.1.2.tgz", + "integrity": "sha512-bYeMWakIYkRwcS/KUjN2hlx6exNdW9oCmBEYi2zHSkZYdDprcEG6MnXJLs+zFYTTHUDb3QR9Ex8gXE5y08dVWA==", + "license": "ISC" + }, "node_modules/@casbin/expression-eval": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@casbin/expression-eval/-/expression-eval-5.3.0.tgz", @@ -1138,6 +1144,31 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", @@ -1145,6 +1176,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -3168,7 +3200,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.1.tgz", "integrity": "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==", "license": "MIT", - "peer": true, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "axios": "^1.3.1", @@ -3323,7 +3354,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.29.tgz", "integrity": "sha512-zkeNRlfiQIH/044r5zphjNzKYxBRC4O00onTdCsH2qq0R30Ixo0gOS9so5TadEbJJCuGNm0Vx3PBE2FG2vkBdA==", "license": "MIT", - "peer": true, "dependencies": { "file-type": "21.3.4", "iterare": "1.2.1", @@ -3370,7 +3400,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.29.tgz", "integrity": "sha512-ANXnZxirMNAY+JuRHCIYIdTfbhX3rizeSbJloub/4EYwpIfCuUoaz9woOofWmywYYc3gsVkLxVZV03gqntI8vA==", "license": "MIT", - "peer": true, "dependencies": { "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", @@ -3410,7 +3439,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/graphql/-/graphql-13.4.4.tgz", "integrity": "sha512-ZM0QLe2MTg4wLA50jtTzc9sAp5zeLqjnFryPYJdgu9ZIXEWjNPffPD8aqtDbAaSDZ3ZFLtos7tS3o3rdsILr6A==", "license": "MIT", - "peer": true, "dependencies": { "@graphql-tools/merge": "9.2.2", "@graphql-tools/schema": "10.0.38", @@ -3489,7 +3517,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-11.0.4.tgz", "integrity": "sha512-LUOlUeSOfbjdIu22QwOmczv2CzJQr9LUBo2mOfbXrGCu2svpr5Hiu71zBFrb/9UC+H8BjGMKbBOq1nEbMF6ZJA==", "license": "MIT", - "peer": true, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "@nestjs/core": "^10.0.0 || ^11.0.0", @@ -3512,7 +3539,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.29.tgz", "integrity": "sha512-OngsAjdzM+SbbmP1XojKfBgl0Lj7u8b7IK5duwpFymzNP5EmjS8ujvqck9M8h0L14I91aRIFSjIpVfHj8WVtqw==", "license": "MIT", - "peer": true, "dependencies": { "cors": "2.8.6", "express": "5.2.1", @@ -3877,7 +3903,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -4141,7 +4166,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4278,7 +4302,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~8.3.0" } @@ -4427,6 +4450,12 @@ "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "license": "MIT" }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -4715,7 +4744,6 @@ "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", @@ -5501,7 +5529,6 @@ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5555,7 +5582,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5794,6 +5820,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", @@ -5806,6 +5833,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "4" }, @@ -5818,6 +5846,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -6101,7 +6130,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6349,19 +6377,6 @@ "@types/webidl-conversions": "*" } }, - "node_modules/casbin-mongoose-adapter/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/casbin-mongoose-adapter/node_modules/bson": { "version": "6.10.4", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", @@ -6371,36 +6386,6 @@ "node": ">=16.20.1" } }, - "node_modules/casbin-mongoose-adapter/node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/casbin-mongoose-adapter/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/casbin-mongoose-adapter/node_modules/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -6710,6 +6695,23 @@ "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.15.1.tgz", + "integrity": "sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -7464,7 +7466,6 @@ "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "packages/*" ], @@ -7524,7 +7525,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7924,7 +7924,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -8353,6 +8352,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=4.0" }, @@ -8883,7 +8883,6 @@ "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-7.4.0.tgz", "integrity": "sha512-xfr+zFb/QYbs4l4ty0dltqiXIp07U6sl+tOKAb0t50/EnQek6CVVBLjETXi+FghElytvgaAWtIOt3EV7zLzIAQ==", "license": "MIT", - "peer": true, "dependencies": { "@graphql-typed-document-node/core": "^3.2.0" }, @@ -8895,7 +8894,6 @@ "version": "2.12.6", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", - "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -9635,7 +9633,6 @@ "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.4.2", "@jest/types": "30.4.1", @@ -10622,6 +10619,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.13.11", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.11.tgz", + "integrity": "sha512-ETER2kMaIFTI/Nh1a8Gk03dUF/SL0VZqtI+CcVHZxp5WIHYwNS7S+uiYZDYCvLy3lOR4/DAD5jf0h5WkePPpqg==", + "license": "MIT" + }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", @@ -11122,7 +11125,6 @@ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.9.2.tgz", "integrity": "sha512-Nz8Rwu9jvADm0pyX0M3mEy8bx3wB5b/tRDI4XnjL7p6tZgmdyL9g0gE/EC9It6/XMIbcvniwYQDKYs91Qm+o+g==", "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.1.0", "kareem": "3.3.0", @@ -11140,31 +11142,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/gaxios": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.0.tgz", - "integrity": "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/mongoose/node_modules/google-logging-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.2.0.tgz", - "integrity": "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=18" - } - }, "node_modules/mongoose/node_modules/mongodb": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.5.0.tgz", @@ -11220,25 +11197,6 @@ "node": ">=20.19.0" } }, - "node_modules/mongoose/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "optional": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -11852,7 +11810,8 @@ "node_modules/pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==", + "peer": true }, "node_modules/picocolors": { "version": "1.1.1", @@ -11986,7 +11945,6 @@ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12097,6 +12055,7 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -12238,8 +12197,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/require-directory": { "version": "2.1.1", @@ -12484,7 +12442,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -12538,7 +12495,6 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -13484,7 +13440,6 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13689,7 +13644,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -13889,7 +13843,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14077,6 +14030,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "peer": true, "engines": { "node": ">= 0.4.0" } @@ -14116,6 +14070,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -14177,7 +14140,6 @@ "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -14425,7 +14387,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/packages/server/package.json b/packages/server/package.json index 0fb74d50..04f4e3f0 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -25,6 +25,7 @@ "@apollo/server": "^5.5.1", "@apollo/subgraph": "^2.14.3", "@as-integrations/express5": "^1.1.2", + "@bu-sail/ra-query-core": "^0.1.2", "@google-cloud/run": "^4.0.0", "@google-cloud/secret-manager": "^7.0.0", "@google-cloud/storage": "^7.22.0", @@ -44,6 +45,8 @@ "@types/passport-jwt": "^4.0.1", "casbin": "^5.51.1", "casbin-mongoose-adapter": "^5.6.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", "csv-parser": "^3.2.1", "firebase-admin": "^14.2.0", "graphql-request": "^7.4.0", diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index 6835c82c..40f67e7f 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -1,6 +1,8 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { ValidationPipe, BadRequestException } from '@nestjs/common'; +import { ValidationError } from 'class-validator'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true }); @@ -14,6 +16,19 @@ async function bootstrap() { const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, documentFactory); + // DTO validation + app.useGlobalPipes( + new ValidationPipe({ + + exceptionFactory: (validationErrors: ValidationError[] = []) => { + console.error(JSON.stringify(validationErrors)); + return new BadRequestException(validationErrors); + }, + transform: true, + whitelist: true + }) + ); + await app.listen(process.env.PORT || 3000); } bootstrap(); diff --git a/packages/server/src/project/controllers/project-admin.controller.ts b/packages/server/src/project/controllers/project-admin.controller.ts index 7475a9c3..8200eb6d 100644 --- a/packages/server/src/project/controllers/project-admin.controller.ts +++ b/packages/server/src/project/controllers/project-admin.controller.ts @@ -1,25 +1,38 @@ -import { Controller, Delete, Post, Put, Get } from '@nestjs/common'; +import { Controller, Delete, Post, Put, Get, Query, Response } from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { FeatureFlag } from '../../feature-flag/feature-flag.decorator'; +import { RAQuery } from '../../shared/ra-query.dto'; +import { Response as Res } from 'express'; +import { ProjectService } from '../project.service'; +import { makeContentRange } from '@bu-sail/ra-query-core'; @Controller('api/admin/project') @ApiTags('Project (Admin)') export class AdminProjectController { + constructor(private readonly projectService: ProjectService) {} + @Post() @ApiOperation({ summary: 'Create a new project' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') async create() {} + @Get() + @ApiOperation({ summary: 'Get many projects' }) + @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') + async getMany(@Query() query: RAQuery, @Response() response: Res): Promise { + // TODO: Get organization ID from auth guard + const projects = await this.projectService.get(query, '6a832a3d17d5333f9f601db3'); + + response.setHeader('Content-Range', makeContentRange('projects', projects)); + + return response.json(projects.data); + } + @Get(':id') @ApiOperation({ summary: 'Get a single project' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') async get() {} - @Get() - @ApiOperation({ summary: 'Get many projects' }) - @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') - async getMany() {} - @Put(':id') @ApiOperation({ summary: 'Update an existing project' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') diff --git a/packages/server/src/project/project.service.ts b/packages/server/src/project/project.service.ts index 3d75b0cf..913f9900 100644 --- a/packages/server/src/project/project.service.ts +++ b/packages/server/src/project/project.service.ts @@ -7,6 +7,8 @@ import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; import { TokenPayload } from 'src/jwt/token.dto'; import { ProjectPermissions } from 'src/permission/permissions/project'; +import { PaginationResponse } from '@bu-sail/ra-query-core'; +import { RAQuery } from '../shared/ra-query.dto'; @Injectable() export class ProjectService { @@ -28,6 +30,19 @@ export class ProjectService { return newProject; } + async get(_query: RAQuery, organizationID: string): Promise> { + // TODO: Handle conversion of RAQuery into Mongoose query + const projects = await this.projectModel.find({ organization: organizationID }); + + // TODO: Include pagination results from proper query usage + return { + data: projects, + count: projects.length, + start: 0, + end: projects.length + } + } + async findById(id: string): Promise { return this.projectModel.findById(id); } diff --git a/packages/server/src/shared/ra-query.dto.ts b/packages/server/src/shared/ra-query.dto.ts new file mode 100644 index 00000000..2f174c07 --- /dev/null +++ b/packages/server/src/shared/ra-query.dto.ts @@ -0,0 +1,36 @@ +import { Transform } from 'class-transformer'; +import { IsOptional } from 'class-validator'; +import { parseRange, parseSort, parseFilter, type FilterItem, type Sort, type Range } from '@bu-sail/ra-query-core'; +import { ApiProperty } from '@nestjs/swagger'; + +export class RAQuery { + @IsOptional() + @Transform(parseSort) + @ApiProperty({ + required: false, + type: String, + description: "Way to determine how to sort the results, follows FakeRest's sort specification", + example: '["title", "asc"]' + }) + sort?: Sort; + + @IsOptional() + @Transform(parseRange) + @ApiProperty({ + required: false, + type: String, + description: "Used to restrict results to fall within a range, follows FakeRest's range specification", + example: '[0-9]' + }) + range?: Range; + + @IsOptional() + @Transform(parseFilter) + @ApiProperty({ + required: false, + type: String, + description: "For filtering results, follows FakeRest's filter specficiation", + example: '{"title": "Section 1-A"}' + }) + filter?: FilterItem[]; +} From 61d3768f1ca00afe4020cbcb2a440adf2fb98d34 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Mon, 17 Aug 2026 12:26:32 -0400 Subject: [PATCH 2/7] Add creation endpoint --- .../project/controllers/project-admin.controller.ts | 13 +++++++++++-- packages/server/src/project/dtos/create.dto.ts | 9 +++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/server/src/project/controllers/project-admin.controller.ts b/packages/server/src/project/controllers/project-admin.controller.ts index 8200eb6d..55f517ba 100644 --- a/packages/server/src/project/controllers/project-admin.controller.ts +++ b/packages/server/src/project/controllers/project-admin.controller.ts @@ -1,10 +1,12 @@ -import { Controller, Delete, Post, Put, Get, Query, Response } from '@nestjs/common'; +import { Controller, Delete, Post, Put, Get, Query, Response, Body } from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { FeatureFlag } from '../../feature-flag/feature-flag.decorator'; import { RAQuery } from '../../shared/ra-query.dto'; import { Response as Res } from 'express'; import { ProjectService } from '../project.service'; import { makeContentRange } from '@bu-sail/ra-query-core'; +import { ProjectCreateV2 } from '../dtos/create.dto'; +import { Project } from '../project.model'; @Controller('api/admin/project') @ApiTags('Project (Admin)') @@ -14,12 +16,19 @@ export class AdminProjectController { @Post() @ApiOperation({ summary: 'Create a new project' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') - async create() {} + async create(@Body() newProject: ProjectCreateV2): Promise { + // TODO: Check permissions + + // TODO: Get organization ID from auth guard + return this.projectService.create(newProject, '6a832a3d17d5333f9f601db3') + } @Get() @ApiOperation({ summary: 'Get many projects' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') async getMany(@Query() query: RAQuery, @Response() response: Res): Promise { + // TODO: Check permissions + // TODO: Get organization ID from auth guard const projects = await this.projectService.get(query, '6a832a3d17d5333f9f601db3'); diff --git a/packages/server/src/project/dtos/create.dto.ts b/packages/server/src/project/dtos/create.dto.ts index 27994df6..995aeea6 100644 --- a/packages/server/src/project/dtos/create.dto.ts +++ b/packages/server/src/project/dtos/create.dto.ts @@ -1,5 +1,14 @@ import { InputType, OmitType } from '@nestjs/graphql'; import { Project } from '../project.model'; +import { ApiProperty } from '@nestjs/swagger'; @InputType() export class ProjectCreate extends OmitType(Project, ['_id', 'created', 'organization'] as const, InputType) {} + +export class ProjectCreateV2 implements Omit { + @ApiProperty({ description: 'Human readable name to identify the project' }) + name!: string; + + @ApiProperty({ description: 'Description of the project for easier recognition' }) + description!: string; +} From 83bb353d260998c08c1ad54641d909f85f7f351e Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Mon, 17 Aug 2026 12:32:30 -0400 Subject: [PATCH 3/7] Add get endpoint --- .../controllers/project-admin.controller.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/server/src/project/controllers/project-admin.controller.ts b/packages/server/src/project/controllers/project-admin.controller.ts index 55f517ba..6527ea2d 100644 --- a/packages/server/src/project/controllers/project-admin.controller.ts +++ b/packages/server/src/project/controllers/project-admin.controller.ts @@ -1,5 +1,5 @@ -import { Controller, Delete, Post, Put, Get, Query, Response, Body } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Controller, Delete, Post, Put, Get, Query, Response, Body, Param, NotFoundException } from '@nestjs/common'; +import { ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; import { FeatureFlag } from '../../feature-flag/feature-flag.decorator'; import { RAQuery } from '../../shared/ra-query.dto'; import { Response as Res } from 'express'; @@ -39,8 +39,17 @@ export class AdminProjectController { @Get(':id') @ApiOperation({ summary: 'Get a single project' }) + @ApiParam({ name: 'id', description: 'ID of the project to retrieve' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') - async get() {} + async get(@Param('id') id: string): Promise { + // TODO: Check permissions + + const project = await this.projectService.findById(id) + if (!project) { + throw new NotFoundException(); + } + return project; + } @Put(':id') @ApiOperation({ summary: 'Update an existing project' }) @@ -50,5 +59,5 @@ export class AdminProjectController { @Delete(':id') @ApiOperation({ summary: 'Delete an existing project' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') - async delete() {} + async delete(): Promise {} } From 87ee033f95feacebe370d58f645dfb809966ab98 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Mon, 17 Aug 2026 12:53:15 -0400 Subject: [PATCH 4/7] Add update endpoint --- .../controllers/project-admin.controller.ts | 8 +++++++- packages/server/src/project/dtos/create.dto.ts | 3 +++ packages/server/src/project/dtos/update.dto.ts | 15 +++++++++++++++ packages/server/src/project/project.service.ts | 17 ++++++++++++++--- 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 packages/server/src/project/dtos/update.dto.ts diff --git a/packages/server/src/project/controllers/project-admin.controller.ts b/packages/server/src/project/controllers/project-admin.controller.ts index 6527ea2d..cd92a9fc 100644 --- a/packages/server/src/project/controllers/project-admin.controller.ts +++ b/packages/server/src/project/controllers/project-admin.controller.ts @@ -7,6 +7,8 @@ import { ProjectService } from '../project.service'; import { makeContentRange } from '@bu-sail/ra-query-core'; import { ProjectCreateV2 } from '../dtos/create.dto'; import { Project } from '../project.model'; +import { ProjectUpdateV2 } from '../dtos/update.dto'; +import { ProjectPipe } from '../pipes/project.pipe'; @Controller('api/admin/project') @ApiTags('Project (Admin)') @@ -53,8 +55,12 @@ export class AdminProjectController { @Put(':id') @ApiOperation({ summary: 'Update an existing project' }) + @ApiParam({ name: 'id', description: 'ID of the project to update', type: String }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') - async update() {} + async update(@Param('id', ProjectPipe) project: Project, @Body() update: ProjectUpdateV2) { + // TODO; Check permissions + return this.projectService.update(project._id, update); + } @Delete(':id') @ApiOperation({ summary: 'Delete an existing project' }) diff --git a/packages/server/src/project/dtos/create.dto.ts b/packages/server/src/project/dtos/create.dto.ts index 995aeea6..b88dd88d 100644 --- a/packages/server/src/project/dtos/create.dto.ts +++ b/packages/server/src/project/dtos/create.dto.ts @@ -1,14 +1,17 @@ import { InputType, OmitType } from '@nestjs/graphql'; import { Project } from '../project.model'; import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; @InputType() export class ProjectCreate extends OmitType(Project, ['_id', 'created', 'organization'] as const, InputType) {} export class ProjectCreateV2 implements Omit { + @IsString() @ApiProperty({ description: 'Human readable name to identify the project' }) name!: string; + @IsString() @ApiProperty({ description: 'Description of the project for easier recognition' }) description!: string; } diff --git a/packages/server/src/project/dtos/update.dto.ts b/packages/server/src/project/dtos/update.dto.ts new file mode 100644 index 00000000..8fd437ac --- /dev/null +++ b/packages/server/src/project/dtos/update.dto.ts @@ -0,0 +1,15 @@ +import { IsOptional, IsString } from 'class-validator'; +import { Project } from '../project.model'; +import { ApiProperty } from '@nestjs/swagger'; + +export class ProjectUpdateV2 implements Partial> { + @IsOptional() + @IsString() + @ApiProperty({ description: 'Human readable name to identify the project', required: false }) + name?: string; + + @IsOptional() + @IsString() + @ApiProperty({ description: 'Description of the project for easier recognition', required: false }) + description?: string; +} diff --git a/packages/server/src/project/project.service.ts b/packages/server/src/project/project.service.ts index 913f9900..ca1560c4 100644 --- a/packages/server/src/project/project.service.ts +++ b/packages/server/src/project/project.service.ts @@ -1,7 +1,7 @@ -import { Injectable, Inject } from '@nestjs/common'; +import { Injectable, Inject, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; -import { Project, ProjectDocument } from './project.model'; +import { Project } from './project.model'; import { ProjectCreate } from './dtos/create.dto'; import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; @@ -9,11 +9,12 @@ import { TokenPayload } from 'src/jwt/token.dto'; import { ProjectPermissions } from 'src/permission/permissions/project'; import { PaginationResponse } from '@bu-sail/ra-query-core'; import { RAQuery } from '../shared/ra-query.dto'; +import { ProjectUpdateV2 } from './dtos/update.dto'; @Injectable() export class ProjectService { constructor( - @InjectModel(Project.name) private projectModel: Model, + @InjectModel(Project.name) private projectModel: Model, @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer ) {} @@ -60,6 +61,16 @@ export class ProjectService { await this.projectModel.findByIdAndDelete(project._id); } + async update(id: string, update: ProjectUpdateV2): Promise { + await this.projectModel.updateOne({ _id: id }, { $set: update }); + + const updated = await this.findById(id); + if (!updated) { + throw new NotFoundException(); + } + return updated; + } + async findAllForUser(user: TokenPayload, organization: string): Promise { await this.enforcer.loadPolicy(); const projects = await this.findAll(organization); From 8a43ef6a7ed78c9484f67b0535406c13fc3699e9 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Mon, 17 Aug 2026 14:27:17 -0400 Subject: [PATCH 5/7] Implement deletion endpoint --- .../src/project/controllers/project-admin.controller.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/project/controllers/project-admin.controller.ts b/packages/server/src/project/controllers/project-admin.controller.ts index cd92a9fc..6fe65e4d 100644 --- a/packages/server/src/project/controllers/project-admin.controller.ts +++ b/packages/server/src/project/controllers/project-admin.controller.ts @@ -65,5 +65,9 @@ export class AdminProjectController { @Delete(':id') @ApiOperation({ summary: 'Delete an existing project' }) @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') - async delete(): Promise {} + @ApiParam({ name: 'id', type: String }) + async delete(@Param('id', ProjectPipe) project: Project): Promise { + await this.projectService.delete(project); + return project; + } } From eda08954a3d4fd790c0f380aaae0596bfc014846 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Mon, 17 Aug 2026 15:09:11 -0400 Subject: [PATCH 6/7] Working project CRUD through UI --- packages/admin/package-lock.json | 13 ++++ packages/admin/package.json | 1 + packages/admin/src/App.tsx | 27 +++++-- .../admin/src/contexts/Config.context.tsx | 14 ++++ .../admin/src/contexts/Config.provider.tsx | 20 ++++++ .../src/dataProviders/MongoIdDataProvider.ts | 70 +++++++++++++++++++ .../src/pages/projects/CreateProject.page.tsx | 12 ++++ .../src/pages/projects/ListProjects.page.tsx | 12 ++++ .../src/pages/projects/ShowProject.page.tsx | 14 ++++ packages/server/src/main.ts | 5 ++ .../controllers/project-admin.controller.ts | 3 +- 11 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 packages/admin/src/contexts/Config.context.tsx create mode 100644 packages/admin/src/contexts/Config.provider.tsx create mode 100644 packages/admin/src/dataProviders/MongoIdDataProvider.ts create mode 100644 packages/admin/src/pages/projects/CreateProject.page.tsx create mode 100644 packages/admin/src/pages/projects/ListProjects.page.tsx create mode 100644 packages/admin/src/pages/projects/ShowProject.page.tsx diff --git a/packages/admin/package-lock.json b/packages/admin/package-lock.json index 02287375..cb350c4d 100644 --- a/packages/admin/package-lock.json +++ b/packages/admin/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "ra-data-json-server": "^5.15.0", + "ra-data-simple-rest": "^5.15.0", "react": "^19.2.8", "react-admin": "^5.15.1", "react-dom": "^19.2.8" @@ -3644,6 +3645,18 @@ "ra-core": "^5.15.0" } }, + "node_modules/ra-data-simple-rest": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/ra-data-simple-rest/-/ra-data-simple-rest-5.15.0.tgz", + "integrity": "sha512-C7WwkUX+xAXPsCiA01cn6SwxNS7eLugwlw7iT8VyoENpQp6Q66nhT6ZXtwh2SOLFdjBk5Tyl6lKIEtrgFkEGgA==", + "license": "MIT", + "dependencies": { + "query-string": "^7.1.3" + }, + "peerDependencies": { + "ra-core": "^5.0.0" + } + }, "node_modules/ra-i18n-polyglot": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/ra-i18n-polyglot/-/ra-i18n-polyglot-5.15.0.tgz", diff --git a/packages/admin/package.json b/packages/admin/package.json index 16ebefcf..e817b99c 100644 --- a/packages/admin/package.json +++ b/packages/admin/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "ra-data-json-server": "^5.15.0", + "ra-data-simple-rest": "^5.15.0", "react": "^19.2.8", "react-admin": "^5.15.1", "react-dom": "^19.2.8" diff --git a/packages/admin/src/App.tsx b/packages/admin/src/App.tsx index 53d44e5a..7dfdda5f 100644 --- a/packages/admin/src/App.tsx +++ b/packages/admin/src/App.tsx @@ -1,15 +1,28 @@ -import { Admin, Resource, ListGuesser } from 'react-admin'; -import jsonServerProvider from 'ra-data-json-server'; +import { Admin, Resource } from 'react-admin'; +import { ConfigProvider } from './contexts/Config.provider'; +import { useConfig } from './contexts/Config.context'; +import { createMongoIdDataProvider } from './dataProviders/MongoIdDataProvider'; +import { ListProjects } from './pages/projects/ListProjects.page'; +import { ShowProject } from './pages/projects/ShowProject.page'; +import { CreateProject } from './pages/projects/CreateProject.page'; -const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com'); +const App: React.FC = () => { + return ( + + + + ); +} -function App() { +const ConfigWrappedInner: React.FC = () => { + // Setup React Admin data provider + const config = useConfig(); + const dataProvider = createMongoIdDataProvider(config.backend.apiBase); return ( - - + ); -} +}; export default App; diff --git a/packages/admin/src/contexts/Config.context.tsx b/packages/admin/src/contexts/Config.context.tsx new file mode 100644 index 00000000..310ee6b4 --- /dev/null +++ b/packages/admin/src/contexts/Config.context.tsx @@ -0,0 +1,14 @@ +import { createContext, useContext } from 'react'; + +export interface ConfigFields { + auth: { + authorityURL: string; + clientID: string; + }; + backend: { + apiBase: string; + } +} + +export const ConfigContext = createContext({} as ConfigFields); +export const useConfig = () => useContext(ConfigContext); diff --git a/packages/admin/src/contexts/Config.provider.tsx b/packages/admin/src/contexts/Config.provider.tsx new file mode 100644 index 00000000..143d1f3a --- /dev/null +++ b/packages/admin/src/contexts/Config.provider.tsx @@ -0,0 +1,20 @@ +import { type ReactNode } from 'react'; +import { ConfigContext, type ConfigFields } from './Config.context'; + +const devValues: ConfigFields = { + auth: { + authorityURL: import.meta.env.VITE_AUTHORITY_URL, + clientID: import.meta.env.VITE_AUTH_CLIENT_ID + }, + backend: { + apiBase: import.meta.env.VITE_BACKEND_API_BASE_URL + } +}; + +export const ConfigProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + return ( + + {children} + + ); +} diff --git a/packages/admin/src/dataProviders/MongoIdDataProvider.ts b/packages/admin/src/dataProviders/MongoIdDataProvider.ts new file mode 100644 index 00000000..34e27ede --- /dev/null +++ b/packages/admin/src/dataProviders/MongoIdDataProvider.ts @@ -0,0 +1,70 @@ +import type { + CreateParams, + DataProvider, + DeleteParams, + GetListParams, + GetManyParams, + GetManyReferenceParams, + GetOneParams, + Identifier, + QueryFunctionContext, + RaRecord, + UpdateParams, +} from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; + +type MongoRecord = RaRecord & { _id: Identifier }; + +// Records coming back from the API use Mongo's "_id" as their primary key. +// React Admin requires every record to have an "id" field, so this maps +// "_id" -> "id" on every response coming out of the underlying REST provider. +// See https://marmelab.com/react-admin/FAQ.html#can-i-have-custom-identifiersprimary-keys-for-my-resources +const toRaRecord = (record: MongoRecord): RaRecord => ({ + ...record, + id: record._id, +}); + +export const createMongoIdDataProvider = (apiBase: string): DataProvider => { + const baseProvider = simpleRestProvider(apiBase); + + const provider = { + ...baseProvider, + getList: (resource: string, params: GetListParams & QueryFunctionContext) => + baseProvider.getList(resource, params).then((response) => ({ + ...response, + data: response.data.map(toRaRecord), + })), + getOne: (resource: string, params: GetOneParams & QueryFunctionContext) => + baseProvider.getOne(resource, params).then((response) => ({ + ...response, + data: toRaRecord(response.data), + })), + getMany: (resource: string, params: GetManyParams & QueryFunctionContext) => + baseProvider.getMany(resource, params).then((response) => ({ + ...response, + data: response.data.map(toRaRecord), + })), + getManyReference: (resource: string, params: GetManyReferenceParams & QueryFunctionContext) => + baseProvider.getManyReference(resource, params).then((response) => ({ + ...response, + data: response.data.map(toRaRecord), + })), + create: (resource: string, params: CreateParams) => + baseProvider.create(resource, params).then((response) => ({ + ...response, + data: toRaRecord(response.data), + })), + update: (resource: string, params: UpdateParams) => + baseProvider.update(resource, params).then((response) => ({ + ...response, + data: toRaRecord(response.data), + })), + delete: (resource: string, params: DeleteParams) => + baseProvider.delete(resource, params).then((response) => ({ + ...response, + data: toRaRecord(response.data), + })), + }; + + return provider as unknown as DataProvider; +}; diff --git a/packages/admin/src/pages/projects/CreateProject.page.tsx b/packages/admin/src/pages/projects/CreateProject.page.tsx new file mode 100644 index 00000000..f178d342 --- /dev/null +++ b/packages/admin/src/pages/projects/CreateProject.page.tsx @@ -0,0 +1,12 @@ +import { Create, required, SimpleForm, TextInput } from 'react-admin'; + +export const CreateProject: React.FC = () => { + return ( + + + + + + + ); +}; diff --git a/packages/admin/src/pages/projects/ListProjects.page.tsx b/packages/admin/src/pages/projects/ListProjects.page.tsx new file mode 100644 index 00000000..c92cd61d --- /dev/null +++ b/packages/admin/src/pages/projects/ListProjects.page.tsx @@ -0,0 +1,12 @@ +import { Datagrid, List, TextField } from "react-admin"; + +export const ListProjects: React.FC = () => { + return ( + + + + + + + ); +}; diff --git a/packages/admin/src/pages/projects/ShowProject.page.tsx b/packages/admin/src/pages/projects/ShowProject.page.tsx new file mode 100644 index 00000000..9f22209f --- /dev/null +++ b/packages/admin/src/pages/projects/ShowProject.page.tsx @@ -0,0 +1,14 @@ +import { Show, SimpleShowLayout, TextField } from "react-admin"; + + +export const ShowProject: React.FC = () => { + + return ( + + + + + + + ); +}; diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index 40f67e7f..42ddfd89 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -7,6 +7,11 @@ import { ValidationError } from 'class-validator'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true }); + // CORs for React Admin content-range header + app.enableCors({ + exposedHeaders: ['Content-Range'] + }); + // Setup OpenAPI (Swagger) const config = new DocumentBuilder() .setTitle('SignTag API') diff --git a/packages/server/src/project/controllers/project-admin.controller.ts b/packages/server/src/project/controllers/project-admin.controller.ts index 6fe65e4d..68a6ec76 100644 --- a/packages/server/src/project/controllers/project-admin.controller.ts +++ b/packages/server/src/project/controllers/project-admin.controller.ts @@ -10,7 +10,7 @@ import { Project } from '../project.model'; import { ProjectUpdateV2 } from '../dtos/update.dto'; import { ProjectPipe } from '../pipes/project.pipe'; -@Controller('api/admin/project') +@Controller('api/v1/admin/projects') @ApiTags('Project (Admin)') export class AdminProjectController { constructor(private readonly projectService: ProjectService) {} @@ -67,6 +67,7 @@ export class AdminProjectController { @FeatureFlag('SIGNTAG_ADMIN_PROJECT_ENDPOINT') @ApiParam({ name: 'id', type: String }) async delete(@Param('id', ProjectPipe) project: Project): Promise { + // TODO: Check permissions await this.projectService.delete(project); return project; } From 8ed7114456df50d7338d5238a8deccde5ade39cf Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Mon, 17 Aug 2026 15:12:43 -0400 Subject: [PATCH 7/7] Fix formatting --- packages/admin/src/App.tsx | 2 +- packages/admin/src/contexts/Config.context.tsx | 2 +- packages/admin/src/contexts/Config.provider.tsx | 8 ++------ packages/admin/src/pages/projects/ListProjects.page.tsx | 6 +++--- packages/admin/src/pages/projects/ShowProject.page.tsx | 8 +++----- packages/server/src/main.ts | 1 - .../src/project/controllers/project-admin.controller.ts | 4 ++-- packages/server/src/project/project.service.ts | 2 +- 8 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/admin/src/App.tsx b/packages/admin/src/App.tsx index 7dfdda5f..104f385d 100644 --- a/packages/admin/src/App.tsx +++ b/packages/admin/src/App.tsx @@ -12,7 +12,7 @@ const App: React.FC = () => { ); -} +}; const ConfigWrappedInner: React.FC = () => { // Setup React Admin data provider diff --git a/packages/admin/src/contexts/Config.context.tsx b/packages/admin/src/contexts/Config.context.tsx index 310ee6b4..72913ad0 100644 --- a/packages/admin/src/contexts/Config.context.tsx +++ b/packages/admin/src/contexts/Config.context.tsx @@ -7,7 +7,7 @@ export interface ConfigFields { }; backend: { apiBase: string; - } + }; } export const ConfigContext = createContext({} as ConfigFields); diff --git a/packages/admin/src/contexts/Config.provider.tsx b/packages/admin/src/contexts/Config.provider.tsx index 143d1f3a..a2c8ed87 100644 --- a/packages/admin/src/contexts/Config.provider.tsx +++ b/packages/admin/src/contexts/Config.provider.tsx @@ -12,9 +12,5 @@ const devValues: ConfigFields = { }; export const ConfigProvider: React.FC<{ children: ReactNode }> = ({ children }) => { - return ( - - {children} - - ); -} + return {children}; +}; diff --git a/packages/admin/src/pages/projects/ListProjects.page.tsx b/packages/admin/src/pages/projects/ListProjects.page.tsx index c92cd61d..8c354934 100644 --- a/packages/admin/src/pages/projects/ListProjects.page.tsx +++ b/packages/admin/src/pages/projects/ListProjects.page.tsx @@ -1,11 +1,11 @@ -import { Datagrid, List, TextField } from "react-admin"; +import { Datagrid, List, TextField } from 'react-admin'; export const ListProjects: React.FC = () => { return ( - - + + ); diff --git a/packages/admin/src/pages/projects/ShowProject.page.tsx b/packages/admin/src/pages/projects/ShowProject.page.tsx index 9f22209f..035a1c80 100644 --- a/packages/admin/src/pages/projects/ShowProject.page.tsx +++ b/packages/admin/src/pages/projects/ShowProject.page.tsx @@ -1,13 +1,11 @@ -import { Show, SimpleShowLayout, TextField } from "react-admin"; - +import { Show, SimpleShowLayout, TextField } from 'react-admin'; export const ShowProject: React.FC = () => { - return ( - - + + ); diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index 42ddfd89..dadc31cf 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -24,7 +24,6 @@ async function bootstrap() { // DTO validation app.useGlobalPipes( new ValidationPipe({ - exceptionFactory: (validationErrors: ValidationError[] = []) => { console.error(JSON.stringify(validationErrors)); return new BadRequestException(validationErrors); diff --git a/packages/server/src/project/controllers/project-admin.controller.ts b/packages/server/src/project/controllers/project-admin.controller.ts index 68a6ec76..8d30671e 100644 --- a/packages/server/src/project/controllers/project-admin.controller.ts +++ b/packages/server/src/project/controllers/project-admin.controller.ts @@ -22,7 +22,7 @@ export class AdminProjectController { // TODO: Check permissions // TODO: Get organization ID from auth guard - return this.projectService.create(newProject, '6a832a3d17d5333f9f601db3') + return this.projectService.create(newProject, '6a832a3d17d5333f9f601db3'); } @Get() @@ -46,7 +46,7 @@ export class AdminProjectController { async get(@Param('id') id: string): Promise { // TODO: Check permissions - const project = await this.projectService.findById(id) + const project = await this.projectService.findById(id); if (!project) { throw new NotFoundException(); } diff --git a/packages/server/src/project/project.service.ts b/packages/server/src/project/project.service.ts index ca1560c4..4798c027 100644 --- a/packages/server/src/project/project.service.ts +++ b/packages/server/src/project/project.service.ts @@ -41,7 +41,7 @@ export class ProjectService { count: projects.length, start: 0, end: projects.length - } + }; } async findById(id: string): Promise {