Web UI support for adding new members to a crate
Diff
chartered-frontend/package-lock.json | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
chartered-frontend/package.json | 3 +++
chartered-db/src/crates.rs | 26 +++++++++++++++++++++++++-
chartered-db/src/users.rs | 18 ++++++++++++++++++
chartered-web/src/main.rs | 7 ++++++-
chartered-frontend/src/pages/crate/Members.tsx | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
chartered-web/src/endpoints/web_api/mod.rs | 2 ++
chartered-web/src/endpoints/web_api/search_users.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
chartered-web/src/endpoints/web_api/crates/members.rs | 23 +++++++++++++++++++++--
chartered-web/src/endpoints/web_api/crates/mod.rs | 3 ++-
10 files changed, 583 insertions(+), 70 deletions(-)
@@ -10,9 +10,11 @@
"license": "0BSD",
"dependencies": {
"bootstrap": "^5.1.1",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-bootstrap": "^2.0.0-beta.6",
"react-bootstrap-icons": "^1.5.0",
"react-bootstrap-typeahead": "^5.2.0",
"react-dom": "^17.0.2",
"react-human-time": "^1.2.0",
"react-markdown": "^7.0.1",
@@ -22,6 +24,7 @@
"source-code-pro": "^2.38.0"
},
"devDependencies": {
"@types/lodash": "^4.14.173",
"@types/react": "^17.0.20",
"@types/react-dom": "^17.0.9",
"parcel-bundler": "^1.12.5",
@@ -1686,6 +1689,19 @@
"node": ">=6.9.0"
}
},
"node_modules/@hypnosphi/create-react-context": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz",
"integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==",
"dependencies": {
"gud": "^1.0.0",
"warning": "^4.0.3"
},
"peerDependencies": {
"prop-types": "^15.0.0",
"react": ">=0.14.0"
}
},
"node_modules/@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
@@ -1868,6 +1884,12 @@
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz",
"integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg=="
},
"node_modules/@types/lodash": {
"version": "4.14.173",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.173.tgz",
"integrity": "sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==",
"dev": true
},
"node_modules/@types/mdast": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -2786,7 +2808,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -3250,6 +3271,11 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"node_modules/compute-scroll-into-view": {
"version": "1.0.17",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
"integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3849,6 +3875,22 @@
"dev": true,
"engines": {
"node": ">=0.10"
}
},
"node_modules/deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
"dependencies": {
"is-arguments": "^1.0.4",
"is-date-object": "^1.0.1",
"is-regex": "^1.0.4",
"object-is": "^1.0.1",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.2.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-extend": {
@@ -3888,7 +3930,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dev": true,
"dependencies": {
"object-keys": "^1.0.12"
},
@@ -4537,8 +4578,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "2.2.7",
@@ -4721,8 +4761,7 @@
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
@@ -4737,7 +4776,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -4874,6 +4912,11 @@
"brfs": "^1.2.0",
"unicode-trie": "^0.3.1"
}
},
"node_modules/gud": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
"integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
},
"node_modules/har-schema": {
"version": "2.0.0",
@@ -4902,7 +4945,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
@@ -4953,7 +4995,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -4965,7 +5006,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dev": true,
"dependencies": {
"has-symbols": "^1.0.2"
},
@@ -5578,6 +5618,21 @@
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
@@ -5695,7 +5750,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"dependencies": {
"has-tostringtag": "^1.0.0"
},
@@ -5897,7 +5951,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
@@ -6206,8 +6259,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.clone": {
"version": "4.5.0",
@@ -6218,8 +6270,7 @@
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
@@ -7533,12 +7584,26 @@
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz",
"integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==",
"dev": true
},
"node_modules/object-is": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -7970,6 +8035,16 @@
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
"dev": true
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/posix-character-classes": {
"version": "0.1.1",
@@ -8873,8 +8948,41 @@
},
"peerDependencies": {
"react": "^16.8.6 || ^17"
}
},
"node_modules/react-bootstrap-typeahead": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-bootstrap-typeahead/-/react-bootstrap-typeahead-5.2.0.tgz",
"integrity": "sha512-tM7HiUX4ra/3lF6YmrXCjIreG5o1RIsAuFQiRKeZKtRWLQ+bQfa+rdKpga2YNj+lvIGgJNsyUtVLSMxyZ3czEg==",
"dependencies": {
"@babel/runtime": "^7.14.6",
"@restart/hooks": "^0.4.0",
"classnames": "^2.2.0",
"fast-deep-equal": "^3.1.1",
"invariant": "^2.2.1",
"lodash.debounce": "^4.0.8",
"prop-types": "^15.5.8",
"react-overlays": "^5.1.0",
"react-popper": "^1.0.0",
"scroll-into-view-if-needed": "^2.2.20",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/react-bootstrap-typeahead/node_modules/@restart/hooks": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.0.tgz",
"integrity": "sha512-+RenTVobiCHPjUTbhQDV8m0PU1xEWqgloMIIOlf86oKnfghKR/l4tKto7TH543shEQZZa7ARSMTvT0cXN9u8+g==",
"dependencies": {
"dequal": "^2.0.2"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -8940,6 +9048,42 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/react-overlays": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.1.1.tgz",
"integrity": "sha512-eCN2s2/+GVZzpnId4XVWtvDPYYBD2EtOGP74hE+8yDskPzFy9+pV1H3ZZihxuRdEbQzzacySaaDkR7xE0ydl4Q==",
"dependencies": {
"@babel/runtime": "^7.13.8",
"@popperjs/core": "^2.8.6",
"@restart/hooks": "^0.3.26",
"@types/warning": "^3.0.0",
"dom-helpers": "^5.2.0",
"prop-types": "^15.7.2",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
},
"peerDependencies": {
"react": ">=16.3.0",
"react-dom": ">=16.3.0"
}
},
"node_modules/react-popper": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz",
"integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==",
"dependencies": {
"@babel/runtime": "^7.1.2",
"@hypnosphi/create-react-context": "^0.3.1",
"deep-equal": "^1.1.1",
"popper.js": "^1.14.4",
"prop-types": "^15.6.1",
"typed-styles": "^0.0.7",
"warning": "^4.0.2"
},
"peerDependencies": {
"react": "0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/react-router": {
"version": "5.2.1",
@@ -9212,6 +9356,21 @@
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/regexp.prototype.flags": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz",
"integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/regexpu-core": {
@@ -9711,6 +9870,14 @@
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"node_modules/scroll-into-view-if-needed": {
"version": "2.2.28",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz",
"integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==",
"dependencies": {
"compute-scroll-into-view": "^1.0.17"
}
},
"node_modules/semver": {
@@ -10896,6 +11063,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/typed-styles": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -12806,6 +12978,15 @@
"requires": {
"@babel/helper-validator-identifier": "^7.14.9",
"to-fast-properties": "^2.0.0"
}
},
"@hypnosphi/create-react-context": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz",
"integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==",
"requires": {
"gud": "^1.0.0",
"warning": "^4.0.3"
}
},
"@iarna/toml": {
@@ -12954,6 +13135,12 @@
"version": "2.2.35",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz",
"integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg=="
},
"@types/lodash": {
"version": "4.14.173",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.173.tgz",
"integrity": "sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==",
"dev": true
},
"@types/mdast": {
"version": "3.0.10",
@@ -13720,7 +13907,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -14096,6 +14282,11 @@
}
}
},
"compute-scroll-into-view": {
"version": "1.0.17",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
"integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -14589,6 +14780,19 @@
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
},
"deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
"requires": {
"is-arguments": "^1.0.4",
"is-date-object": "^1.0.1",
"is-regex": "^1.0.4",
"object-is": "^1.0.1",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.2.0"
}
},
"deep-extend": {
"version": "0.6.0",
@@ -14623,7 +14827,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dev": true,
"requires": {
"object-keys": "^1.0.12"
}
@@ -15155,8 +15358,7 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-glob": {
"version": "2.2.7",
@@ -15300,8 +15502,7 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"gensync": {
"version": "1.0.0-beta.2",
@@ -15313,7 +15514,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -15422,6 +15622,11 @@
"brfs": "^1.2.0",
"unicode-trie": "^0.3.1"
}
},
"gud": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
"integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
},
"har-schema": {
"version": "2.0.0",
@@ -15443,7 +15648,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
@@ -15480,14 +15684,12 @@
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"dev": true
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dev": true,
"requires": {
"has-symbols": "^1.0.2"
}
@@ -15961,6 +16163,15 @@
"requires": {
"is-alphabetical": "^2.0.0",
"is-decimal": "^2.0.0"
}
},
"is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
}
},
"is-arrayish": {
@@ -16053,7 +16264,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
@@ -16183,7 +16393,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
@@ -16425,8 +16634,7 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.clone": {
"version": "4.5.0",
@@ -16437,8 +16645,7 @@
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"lodash.memoize": {
"version": "4.1.2",
@@ -17365,12 +17572,20 @@
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz",
"integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==",
"dev": true
},
"object-is": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"object-visit": {
"version": "1.0.1",
@@ -17721,6 +17936,11 @@
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
"dev": true
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
},
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -18463,6 +18683,34 @@
"integrity": "sha512-QC5q4meHQG0cO9RJzeDLSqZ1gbVa9jxFCpONCE3GYl2FkbAKSyJAEsONlzTApbZ8/oG87gPWq0xAyn5SZ/Jafw==",
"requires": {
"prop-types": "^15.7.2"
}
},
"react-bootstrap-typeahead": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-bootstrap-typeahead/-/react-bootstrap-typeahead-5.2.0.tgz",
"integrity": "sha512-tM7HiUX4ra/3lF6YmrXCjIreG5o1RIsAuFQiRKeZKtRWLQ+bQfa+rdKpga2YNj+lvIGgJNsyUtVLSMxyZ3czEg==",
"requires": {
"@babel/runtime": "^7.14.6",
"@restart/hooks": "^0.4.0",
"classnames": "^2.2.0",
"fast-deep-equal": "^3.1.1",
"invariant": "^2.2.1",
"lodash.debounce": "^4.0.8",
"prop-types": "^15.5.8",
"react-overlays": "^5.1.0",
"react-popper": "^1.0.0",
"scroll-into-view-if-needed": "^2.2.20",
"warning": "^4.0.1"
},
"dependencies": {
"@restart/hooks": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.0.tgz",
"integrity": "sha512-+RenTVobiCHPjUTbhQDV8m0PU1xEWqgloMIIOlf86oKnfghKR/l4tKto7TH543shEQZZa7ARSMTvT0cXN9u8+g==",
"requires": {
"dequal": "^2.0.2"
}
}
}
},
"react-dom": {
@@ -18520,6 +18768,35 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}
}
},
"react-overlays": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.1.1.tgz",
"integrity": "sha512-eCN2s2/+GVZzpnId4XVWtvDPYYBD2EtOGP74hE+8yDskPzFy9+pV1H3ZZihxuRdEbQzzacySaaDkR7xE0ydl4Q==",
"requires": {
"@babel/runtime": "^7.13.8",
"@popperjs/core": "^2.8.6",
"@restart/hooks": "^0.3.26",
"@types/warning": "^3.0.0",
"dom-helpers": "^5.2.0",
"prop-types": "^15.7.2",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
}
},
"react-popper": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz",
"integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==",
"requires": {
"@babel/runtime": "^7.1.2",
"@hypnosphi/create-react-context": "^0.3.1",
"deep-equal": "^1.1.1",
"popper.js": "^1.14.4",
"prop-types": "^15.6.1",
"typed-styles": "^0.0.7",
"warning": "^4.0.2"
}
},
"react-router": {
@@ -18735,6 +19012,15 @@
"is-plain-object": "^2.0.4"
}
}
}
},
"regexp.prototype.flags": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz",
"integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
},
"regexpu-core": {
@@ -19126,6 +19412,14 @@
"object-assign": "^4.1.1"
}
},
"scroll-into-view-if-needed": {
"version": "2.2.28",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz",
"integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==",
"requires": {
"compute-scroll-into-view": "^1.0.17"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -20103,6 +20397,11 @@
"requires": {
"prelude-ls": "~1.1.2"
}
},
"typed-styles": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
},
"typedarray": {
"version": "0.0.6",
@@ -12,6 +12,7 @@
"author": "",
"license": "0BSD",
"devDependencies": {
"@types/lodash": "^4.14.173",
"@types/react": "^17.0.20",
"@types/react-dom": "^17.0.9",
"parcel-bundler": "^1.12.5",
@@ -21,9 +22,11 @@
},
"dependencies": {
"bootstrap": "^5.1.1",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-bootstrap": "^2.0.0-beta.6",
"react-bootstrap-icons": "^1.5.0",
"react-bootstrap-typeahead": "^5.2.0",
"react-dom": "^17.0.2",
"react-human-time": "^1.2.0",
"react-markdown": "^7.0.1",
@@ -222,6 +222,30 @@
.await?
}
pub async fn insert_permissions(
self: Arc<Self>,
conn: ConnectionPool,
given_user_id: i32,
given_permissions: crate::users::UserCratePermissionValue,
) -> Result<usize> {
tokio::task::spawn_blocking(move || {
use crate::schema::user_crate_permissions::dsl::{
crate_id, permissions, user_crate_permissions, user_id,
};
let conn = conn.get()?;
Ok(diesel::insert_into(user_crate_permissions)
.values((
user_id.eq(given_user_id),
crate_id.eq(self.id),
permissions.eq(given_permissions.bits()),
))
.execute(&conn)?)
})
.await?
}
pub async fn delete_member(
self: Arc<Self>,
conn: ConnectionPool,
@@ -237,7 +261,7 @@
diesel::delete(
user_crate_permissions
.filter(user_id.eq(given_user_id))
.filter(crate_id.eq(self.id))
.filter(crate_id.eq(self.id)),
)
.execute(&conn)?;
@@ -16,6 +16,24 @@
}
impl User {
pub async fn search(
conn: ConnectionPool,
given_query: String,
limit: i64,
) -> Result<Vec<User>> {
use crate::schema::users::dsl::username;
tokio::task::spawn_blocking(move || {
let conn = conn.get()?;
Ok(crate::schema::users::table
.filter(username.like(format!("%{}%", given_query)))
.limit(limit)
.load(&conn)?)
})
.await?
}
pub async fn find_by_username(
conn: ConnectionPool,
given_username: String,
@@ -76,12 +76,17 @@
)
.route(
"/crates/:crate/members",
patch(endpoints::web_api::crates::update_members)
patch(endpoints::web_api::crates::update_member)
)
.route(
"/crates/:crate/members",
put(endpoints::web_api::crates::insert_member)
)
.route(
"/crates/:crate/members",
delete(endpoints::web_api::crates::delete_member)
)
.route("/users/search", get(endpoints::web_api::search_users))
.route("/ssh-key", get(endpoints::web_api::get_ssh_keys))
.route("/ssh-key", put(endpoints::web_api::add_ssh_key))
.route("/ssh-key/:id", delete(endpoints::web_api::delete_ssh_key)))
@@ -1,9 +1,12 @@
import React = require("react");
import { useState } from "react";
import { PersonPlus, Trash, CheckLg, Save, PlusLg } from 'react-bootstrap-icons';
import { authenticatedEndpoint, useAuthenticatedRequest } from "../../util";
import { useAuth } from "../../useAuth";
import { Button, Modal } from "react-bootstrap";
import { AsyncTypeahead } from "react-bootstrap-typeahead";
import { debounce } from "lodash";
import _ = require("lodash");
interface CratesMembersResponse {
allowed_permissions: string[],
@@ -23,6 +26,15 @@
auth,
endpoint: `crates/${crate}/members`,
}, [reload]);
const [prospectiveMembers, setProspectiveMembers] = useState([]);
React.useEffect(() => {
if (response && response.members) {
setProspectiveMembers(prospectiveMembers.filter((prospectiveMember) => {
_.findIndex(response.members, (responseMember) => responseMember.id === prospectiveMember.id) === -1
}));
}
}, [response])
if (error) {
return <>{error}</>;
@@ -37,50 +49,49 @@
const allowedPermissions = response.allowed_permissions;
return <div className="container-fluid g-0">
<div className="table-responsive">
<div className={ ""}>
<table className="table table-striped">
<tbody>
{response.members.map((member, index) =>
<MemberListItem
key={index}
crate={crate}
member={member}
prospectiveMember={false}
allowedPermissions={allowedPermissions}
onUpdateComplete={() => setReload(reload + 1)}
/>
)}
{prospectiveMembers.map((member, index) =>
<MemberListItem
key={index}
crate={crate}
member={member}
prospectiveMember={true}
allowedPermissions={allowedPermissions}
onUpdateComplete={() => setReload(reload + 1)}
/>
)}
<tr>
<td className="align-middle fit">
<div
className="d-flex align-items-center justify-content-center rounded-circle"
style={{ width: '48px', height: '48px', background: '#DEDEDE', fontSize: '1rem' }}
>
<PersonPlus />
</div>
</td>
<td className="align-middle">
<input type="search" className="form-control" placeholder="Search for User" />
</td>
<td className="align-middle">
<RenderPermissions allowedPermissions={allowedPermissions} selectedPermissions={[]} userId={-1} />
</td>
<td className="align-middle">
<button type="button" className="btn text-dark pe-none">
<PlusLg />
</button>
</td>
</tr>
<MemberListInserter
onInsert={(username, userId) => setProspectiveMembers([
...prospectiveMembers,
{
id: userId,
username,
permissions: ["VISIBLE"],
}
])}
existingMembers={response.members}
/>
</tbody>
</table>
</div>
</div>;
}
function MemberListItem({ crate, member, allowedPermissions, onUpdateComplete }: { crate: string, member: Member, allowedPermissions: string[], onUpdateComplete: () => any }) {
function MemberListItem({ crate, member, prospectiveMember, allowedPermissions, onUpdateComplete }: { crate: string, member: Member, prospectiveMember: boolean, allowedPermissions: string[], onUpdateComplete: () => any }) {
const auth = useAuth();
const [selectedPermissions, setSelectedPermissions] = useState(member.permissions);
const [deleting, setDeleting] = useState(false);
@@ -94,7 +105,7 @@
try {
let res = await fetch(authenticatedEndpoint(auth, `crates/${crate}/members`), {
method: 'PATCH',
method: prospectiveMember ? 'PUT' : 'PATCH',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
@@ -152,11 +163,11 @@
<span className="visually-hidden">Loading...</span>
</div>
</button>;
} else if (selectedPermissions.indexOf("VISIBLE") === -1) {
} else if (!prospectiveMember && selectedPermissions.indexOf("VISIBLE") === -1) {
itemAction = <button type="button" className="btn text-danger" onClick={() => setDeleting(true)}>
<Trash />
</button>;
} else if (selectedPermissions.sort().join(',') != member.permissions.sort().join(',')) {
} else if (prospectiveMember || selectedPermissions.sort().join(',') != member.permissions.sort().join(',')) {
itemAction = <button type="button" className="btn text-success" onClick={saveUserPermissions}>
<CheckLg />
</button>;
@@ -194,6 +205,83 @@
</td>
</tr>
</>;
}
function MemberListInserter({ onInsert, existingMembers }: { existingMembers: Member[], onInsert: (username, user_id) => any }) {
const auth = useAuth();
const searchRef = React.useRef(null);
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
const [error, setError] = useState("");
const handleSearch = async (query) => {
setLoading(true);
setError("");
try {
let res = await fetch(authenticatedEndpoint(auth, `users/search?q=` + encodeURIComponent(query)));
let json = await res.json();
if (json.error) {
throw new Error(json.error);
}
setOptions(json.users || []);
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
const handleChange = (selected) => {
onInsert(selected[0].username, selected[0].user_id);
searchRef.current.clear();
}
return <tr>
<td className="align-middle fit">
<div
className="d-flex align-items-center justify-content-center rounded-circle"
style={{ width: '48px', height: '48px', background: '#DEDEDE', fontSize: '1rem' }}
>
<PersonPlus />
</div>
</td>
<td className="align-middle">
<AsyncTypeahead
id="search-new-user"
onSearch={handleSearch}
filterBy={(option) => _.findIndex(existingMembers, (existing) => option.user_id === existing.id) === -1}
labelKey="username"
options={options}
isLoading={loading}
placeholder="Search for User"
onChange={handleChange}
ref={searchRef}
renderMenuItemChildren={(option, props) => <>
<img
alt={option.username}
src="http://placekitten.com/24/24"
className="rounded-circle me-2"
/>
<span>{option.username}</span>
</>}
/>
<div className="text-danger">{error}</div>
</td>
<td className="align-middle">
</td>
<td className="align-middle">
<button type="button" className="btn text-dark pe-none">
<PlusLg />
</button>
</td>
</tr>;
}
function RenderPermissions({ allowedPermissions, selectedPermissions, userId, onChange }: { allowedPermissions: string[], selectedPermissions: string[], userId: number, onChange: (permissions) => any }) {
@@ -1,8 +1,10 @@
pub mod crates;
mod login;
mod search_users;
mod ssh_key;
pub use login::handle as login;
pub use search_users::handle as search_users;
pub use ssh_key::{
handle_delete as delete_ssh_key, handle_get as get_ssh_keys, handle_put as add_ssh_key,
};
@@ -1,0 +1,54 @@
use axum::{extract, Json};
use chartered_db::{users::User, ConnectionPool};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Deserialize)]
pub struct RequestParams {
q: String,
}
#[derive(Serialize)]
pub struct Response {
users: Vec<ResponseUser>,
}
#[derive(Serialize)]
pub struct ResponseUser {
user_id: i32,
username: String,
}
pub async fn handle(
extract::Extension(db): extract::Extension<ConnectionPool>,
extract::Query(req): extract::Query<RequestParams>,
) -> Result<Json<Response>, Error> {
let users = User::search(db, req.q, 5)
.await?
.into_iter()
.map(|user| ResponseUser {
user_id: user.id,
username: user.username,
})
.collect();
Ok(Json(Response { users }))
}
#[derive(Error, Debug)]
pub enum Error {
#[error("Failed to query database")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
use axum::http::StatusCode;
match self {
Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
define_error_response!(Error);
@@ -52,7 +52,7 @@
}
#[derive(Deserialize)]
pub struct PatchRequest {
pub struct PutOrPatchRequest {
user_id: i32,
permissions: Permission,
}
@@ -61,7 +61,7 @@
extract::Path((_session_key, name)): extract::Path<(String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
extract::Extension(user): extract::Extension<Arc<User>>,
extract::Json(req): extract::Json<PatchRequest>,
extract::Json(req): extract::Json<PutOrPatchRequest>,
) -> Result<Json<ErrorResponse>, Error> {
let crate_ = Crate::find_by_name(db.clone(), name)
.await?
@@ -75,6 +75,25 @@
if affected_rows == 0 {
return Err(Error::UpdateConflictRemoved);
}
Ok(Json(ErrorResponse { error: None }))
}
pub async fn handle_put(
extract::Path((_session_key, name)): extract::Path<(String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
extract::Extension(user): extract::Extension<Arc<User>>,
extract::Json(req): extract::Json<PutOrPatchRequest>,
) -> Result<Json<ErrorResponse>, Error> {
let crate_ = Crate::find_by_name(db.clone(), name)
.await?
.ok_or(Error::NoCrate)
.map(std::sync::Arc::new)?;
ensure_has_crate_perm!(db, user, crate_, Permission::VISIBLE | -> Error::NoCrate, Permission::MANAGE_USERS | -> Error::NoPermission);
crate_
.insert_permissions(db, req.user_id, req.permissions)
.await?;
Ok(Json(ErrorResponse { error: None }))
}
@@ -1,7 +1,8 @@
mod info;
mod members;
pub use info::handle as info;
pub use members::{
handle_delete as delete_member, handle_get as get_members, handle_patch as update_members,
handle_delete as delete_member, handle_get as get_members, handle_patch as update_member,
handle_put as insert_member,
};