diff --git a/package-lock.json b/package-lock.json index 3882299..e8057d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,33 @@ "crypto-js": "3.1.9-1" } }, + "@azure/ms-rest-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.0.4.tgz", + "integrity": "sha512-nSOPt6st0RtxclYBQV65qXZpvMDqiDQssktvB/SMTAJ5bIytSPtBmlttTTigO5qHvwQcfzzpQE0sMceK+dJ/IQ==", + "requires": { + "@types/node-fetch": "^2.3.7", + "@types/tunnel": "0.0.1", + "abort-controller": "^3.0.0", + "form-data": "^2.5.0", + "node-fetch": "^2.6.0", + "tough-cookie": "^3.0.1", + "tslib": "^1.10.0", + "tunnel": "0.0.6", + "uuid": "^3.3.2", + "xml2js": "^0.4.19" + } + }, + "@azure/storage-blob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-10.5.0.tgz", + "integrity": "sha512-67+0EP7STy9BQgzvN1RgmSvXhxRd044eDgepX7zBp7XslBxz8YGo2cSLm9w5o5Qf1FLCRlwuziRMikaPCLMpVw==", + "requires": { + "@azure/ms-rest-js": "^2.0.0", + "events": "^3.0.0", + "tslib": "^1.9.3" + } + }, "@hapi/bourne": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", @@ -72,8 +99,23 @@ "@types/node": { "version": "12.7.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz", - "integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==", - "dev": true + "integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==" + }, + "@types/node-fetch": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.0.tgz", + "integrity": "sha512-TLFRywthBgL68auWj+ziWu+vnmmcHCDFC/sqCOQf1xTz4hRq8cu79z8CtHU9lncExGBsB8fXA4TiLDLt6xvMzw==", + "requires": { + "@types/node": "*" + } + }, + "@types/tunnel": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.1.tgz", + "integrity": "sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==", + "requires": { + "@types/node": "*" + } }, "@types/uuid": { "version": "3.4.5", @@ -90,6 +132,14 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "abstract-logging": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-1.0.0.tgz", @@ -222,6 +272,11 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -536,6 +591,14 @@ "text-hex": "1.0.x" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -655,7 +718,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" } @@ -701,6 +763,11 @@ } } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -796,7 +863,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", @@ -810,7 +876,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -823,6 +888,16 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -1141,6 +1216,16 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -1711,8 +1796,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==" }, "get-stream": { "version": "4.1.0", @@ -1795,7 +1879,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" } @@ -1809,8 +1892,7 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-value": { "version": "1.0.0", @@ -1918,6 +2000,11 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", @@ -1966,8 +2053,7 @@ "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-ci": { "version": "1.2.1", @@ -2001,8 +2087,7 @@ "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-descriptor": { "version": "0.1.6", @@ -2120,7 +2205,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "^1.0.1" } @@ -2140,7 +2224,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -2403,6 +2486,19 @@ "reusify": "^1.0.2" } }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2439,6 +2535,11 @@ } } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -2621,8 +2722,7 @@ "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", @@ -2633,6 +2733,15 @@ "isobject": "^3.0.0" } }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -2829,6 +2938,11 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "psl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + }, "pstree.remy": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", @@ -3036,6 +3150,11 @@ "ret": "~0.2.0" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "secure-json-parse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-1.0.0.tgz", @@ -3525,6 +3644,16 @@ "nopt": "~1.0.10" } }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "triple-beam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", @@ -3535,6 +3664,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, "typescript": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", @@ -3700,6 +3834,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -3821,6 +3964,21 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "xml2js": { + "version": "0.4.22", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.22.tgz", + "integrity": "sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw==", + "requires": { + "sax": ">=0.6.0", + "util.promisify": "~1.0.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", diff --git a/package.json b/package.json index a053760..dde20ad 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@azure/cosmos": "^3.1.0", + "@azure/storage-blob": "^10.5.0", "argon2": "^0.24.0", "dotenv": "^8.0.0", "fastify": "^2.7.1", @@ -29,6 +30,7 @@ "fastify-helmet": "^3.0.1", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.15", + "moment": "^2.24.0", "uuid": "^3.3.2", "winston": "^3.2.1" } diff --git a/src/plugins/api/authentication.ts b/src/plugins/api/authentication.ts index 63b2e6d..5fd79c8 100644 --- a/src/plugins/api/authentication.ts +++ b/src/plugins/api/authentication.ts @@ -19,8 +19,7 @@ import { badRequestError, badRequestFormError, unauthorizedError, serverError } import { tokenFromHeader } from '../../lib/http' import { User, UserToken, Group, GroupInvitation, GroupPartial, GroupMembership } from '../../types/collections' - -interface PluginOptions {} +import { PluginOptions } from '../../types' function registerRoute(server: FastifyInstance) { interface Body { @@ -119,9 +118,8 @@ function registerRoute(server: FastifyInstance) { + interface Body { + name: string + } + + const options: RouteShorthandOptions = { + schema: { + body: { + type: 'object', + required: ['name'], + properties: { + name: { + type: 'string', + maxLength: MAX_NAME_LENGTH, + }, + }, + }, + response: { + 200: { + type: 'object', + properties: { + id: { type: 'string' }, + available: { type: 'boolean' }, + }, + }, + 400: errorSchema, + }, + }, + } + + server.post('/api/group/available', options, async (request, reply) => { + if (!server.database) return serverError(reply) + + const id = normalize(request.body.name) + + const group = await getItem({ + container: containerFor(server.database.client, 'Groups'), + id, + logger: request.log, + }) + + return { + id, + available: !!group, + } + }) +} function createRoute(server: FastifyInstance) { interface Body { name: string about?: string - open: boolean - requiresApproval: boolean + registration: GroupRegistrationType } const options: RouteShorthandOptions = { schema: { body: { type: 'object', - required: ['name', 'open', 'requiresApproval'], + required: ['name', 'registration'], properties: { name: { type: 'string', @@ -38,8 +84,10 @@ function createRoute(server: FastifyInstance({ @@ -83,8 +131,7 @@ function createRoute(server: FastifyInstance) { interface Query { sort?: string - requiresApproval?: boolean + registration?: GroupRegistrationType continuation?: string } @@ -356,6 +402,10 @@ function listRoute(server: FastifyInstance('/api/groups', options, async (request, reply) => { if (!server.database) return serverError(reply) - const { sort = 'members', requiresApproval, continuation } = request.query - let requiresApprovalString = '' + const { sort = 'members', registration, continuation } = request.query + let registrationString = '' - if (requiresApproval !== undefined) { - requiresApprovalString = `AND d.requiresApproval = ${requiresApproval.toString()}` + if (registration) { + registrationString = `AND d.registration = ${registration}` } const container = containerFor(server.database.client, 'GroupDirectory') const { resources: groups, requestCharge, continuation: newContinuation } = await container.items.query( - `SELECT * FROM GroupDirectory d WHERE d.pk = 'pk' AND d.open = true ${requiresApprovalString} ORDER BY d.${sort}`, + `SELECT * FROM GroupDirectory d WHERE d.pk = 'pk' AND d.active = true ${registrationString} ORDER BY d.${sort}`, { maxItemCount: 40, continuation, @@ -403,6 +453,7 @@ function listRoute(server: FastifyInstance = async server => { + availabilityRoute(server) createRoute(server) getRoute(server) blockRoute(server) diff --git a/src/plugins/api/index.ts b/src/plugins/api/index.ts index ff1e1fd..1593958 100644 --- a/src/plugins/api/index.ts +++ b/src/plugins/api/index.ts @@ -8,8 +8,11 @@ import { tokenFromHeader } from '../../lib/http' import authentication from './authentication' import groups from './groups' import posts from './posts' +import uploads from './uploads' import users from './users' +import { PluginOptions } from '../../types' + interface Database { client: CosmosClient } @@ -28,10 +31,6 @@ declare module "fastify" { } } -interface PluginOptions { - -} - const plugin: Plugin = async server => { const database: Database = { client: new CosmosClient({ @@ -71,6 +70,7 @@ const plugin: Plugin = a server.register(authentication) server.register(groups) server.register(posts) + server.register(uploads) server.register(users) } diff --git a/src/plugins/api/posts.ts b/src/plugins/api/posts.ts index aef03b3..d6d25d3 100644 --- a/src/plugins/api/posts.ts +++ b/src/plugins/api/posts.ts @@ -29,7 +29,7 @@ import { Status, } from '../../types/collections' -interface PluginOptions {} +import { PluginOptions } from '../../types' function doPostRoute(server: FastifyInstance) { interface Body { diff --git a/src/plugins/api/uploads.ts b/src/plugins/api/uploads.ts new file mode 100644 index 0000000..f183410 --- /dev/null +++ b/src/plugins/api/uploads.ts @@ -0,0 +1,49 @@ +import { + FastifyInstance, + Plugin, + DefaultQuery, + DefaultParams, + DefaultHeaders, + DefaultBody, + RouteShorthandOptions, +} from 'fastify' + +import { Server, IncomingMessage, ServerResponse } from 'http' +import moment from 'moment' +import { SharedKeyCredential, ContainerSASPermissions, generateBlobSASQueryParameters } from '@azure/storage-blob' + +import { PluginOptions } from '../../types' + +function getSASRoute(server: FastifyInstance) { + const options: RouteShorthandOptions = { + schema: { + response: { + 200: { + type: 'object', + properties: { + sas: { type: 'string' }, + }, + }, + }, + }, + } + + server.post('/api/sas', options, async () => { + const sharedKeyCredential = new SharedKeyCredential(process.env.BLOB_STORAGE_ACCOUNT!, process.env.BLOB_STORAGE_ACCOUNT_KEY!) + + return { + sas: generateBlobSASQueryParameters({ + containerName: process.env.BLOB_STORAGE_CONTAINER!, + permissions: ContainerSASPermissions.parse('ar').toString(), + startTime: new Date(), + expiryTime: moment().add(5, 'm').toDate(), + }, sharedKeyCredential).toString() + } + }) +} + +const plugin: Plugin = async server => { + getSASRoute(server) +} + +export default plugin diff --git a/src/plugins/api/users.ts b/src/plugins/api/users.ts index f0c23cd..d7ebe95 100644 --- a/src/plugins/api/users.ts +++ b/src/plugins/api/users.ts @@ -14,16 +14,60 @@ import { MAX_NAME_LENGTH } from '../../constants' import { userSchema, errorSchema } from '../../schemas' import { unauthorizedError, serverError, notFoundError, badRequestError } from '../../lib/errors' import { getUserBlocks } from '../../lib/collections' -import { containerFor, createQuerySpec, queryItems, getItem } from '../../lib/database' +import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database' + import { User, UserSubscription, UserBlock, GroupBlock, UserPrivacyType } from '../../types/collections' +import { PluginOptions } from '../../types' -interface PluginOptions {} +function availabilityRoute(server: FastifyInstance) { + interface Body { + name: string + } -function updateRoute(server: FastifyInstance) { - interface Headers { - authorization: string + const options: RouteShorthandOptions = { + schema: { + body: { + type: 'object', + required: ['name'], + properties: { + name: { + type: 'string', + maxLength: MAX_NAME_LENGTH, + }, + }, + }, + response: { + 200: { + type: 'object', + properties: { + id: { type: 'string' }, + available: { type: 'boolean' }, + }, + }, + 400: errorSchema, + }, + }, } + server.post('/api/user/available', options, async (request, reply) => { + if (!server.database) return serverError(reply) + + const id = normalize(request.body.name) + + const user = await getItem({ + container: containerFor(server.database.client, 'Users'), + id, + logger: request.log, + }) + + return { + id, + available: !!user, + } + }) +} + +function updateRoute(server: FastifyInstance) { interface Body { name?: string about?: string @@ -33,12 +77,6 @@ function updateRoute(server: FastifyInstance('/api/self', options, async (request, reply) => { + server.put('/api/self', options, async (request, reply) => { if (!server.database) return serverError(reply) if (!request.viewer) return unauthorizedError(reply) @@ -409,6 +447,7 @@ function unblockRoute(server: FastifyInstance = async server => { + availabilityRoute(server) updateRoute(server) getRoute(server) subscribeRoute(server) diff --git a/src/types/collections.ts b/src/types/collections.ts index 414b117..0d21f4a 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -17,6 +17,7 @@ export type UserItemType = 'user' | 'token' | 'post' | 'follow' | 'timeline' export type UserPrivacyType = 'public' | 'group' | 'subscribers' | 'private' export type UserTransactionType = 'purchase' | 'award' export type GroupStatus = 'pending' | 'paid' +export type GroupRegistrationType = 'open' | 'approval' | 'closed' export type GroupItemType = 'group' | 'membership' | 'report' | 'block' export type GroupMembershipType = 'admin' | 'moderator' | 'member' export type ReportStatus = 'pending' | 'complete' @@ -26,8 +27,7 @@ export interface GroupListing { id: string pk: 'pk' name: string - open: boolean - requiresApproval: boolean + registration: GroupRegistrationType members: number posts: number awards: number @@ -46,8 +46,7 @@ export interface Group { codeOfConduct?: string imageUrl?: string coverImageUrl?: string - open: boolean - requiresApproval: boolean + registration: GroupRegistrationType status: GroupStatus active: boolean created: number diff --git a/src/types/index.ts b/src/types/index.ts index 98c7948..74c766b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -24,3 +24,5 @@ export interface AwardDefinition { cost: number reward: number } + +export interface PluginOptions {}