Dwayne Harris 5 years ago
parent
commit
394db5a71a
  1. 194
      package-lock.json
  2. 2
      package.json
  3. 8
      src/plugins/api/authentication.ts
  4. 87
      src/plugins/api/groups.ts
  5. 8
      src/plugins/api/index.ts
  6. 2
      src/plugins/api/posts.ts
  7. 49
      src/plugins/api/uploads.ts
  8. 63
      src/plugins/api/users.ts
  9. 7
      src/types/collections.ts
  10. 2
      src/types/index.ts

194
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",

2
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"
}

8
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<Server, IncomingMessage, ServerResponse>) {
interface Body {
@ -119,9 +118,8 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
if (invitations.length === 0) return badRequestFormError(reply, 'invitation', 'Invalid invitation code')
}
if (!group.open && !invitation) return badRequestFormError(reply, 'group', 'Group registration closed')
if (group.requiresApproval) userPending = true
if (group.registration === 'closed' && !invitation) return badRequestFormError(reply, 'group', 'Group registration closed')
if (group.registration === 'approval') userPending = true
groupPartial = {
id: group.id,

87
src/plugins/api/groups.ts

@ -14,23 +14,69 @@ import { MIN_ID_LENGTH, MAX_NAME_LENGTH } from '../../constants'
import { errorSchema, groupListingSchema } from '../../schemas'
import { unauthorizedError, badRequestError, notFoundError, serverError } from '../../lib/errors'
import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database'
import { User, Group, GroupListing, GroupMembership, UserBlock, GroupBlock } from '../../types/collections'
import { User, Group, GroupListing, GroupMembership, UserBlock, GroupBlock, GroupRegistrationType } from '../../types/collections'
import { PluginOptions } from '../../types'
interface PluginOptions {}
function availabilityRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
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<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/api/group/available', options, async (request, reply) => {
if (!server.database) return serverError(reply)
const id = normalize(request.body.name)
const group = await getItem<Group>({
container: containerFor(server.database.client, 'Groups'),
id,
logger: request.log,
})
return {
id,
available: !!group,
}
})
}
function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
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<Server, IncomingMessage, ServerResp
maxLength: MAX_NAME_LENGTH,
},
about: { type: 'string' },
open: { type: 'boolean' },
requiresApproval: { type: 'boolean' },
registration: {
type: 'string',
enum: ['open', 'approval', 'closed'],
},
},
},
response: {
@ -66,7 +114,7 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
if (viewer.group) return badRequestError(reply)
const { name, about, open, requiresApproval } = request.body
const { name, about, registration } = request.body
const id = normalize(name)
const existingGroup = await getItem<Group>({
@ -83,8 +131,7 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
userId: request.viewer.id,
name,
about,
open,
requiresApproval,
registration,
status: 'pending',
active: true,
created: Date.now(),
@ -325,8 +372,7 @@ function activateRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
id: group.id,
name: group.name,
pk: 'pk',
open: group.open,
requiresApproval: group.requiresApproval,
registration: group.registration,
members: 0,
posts: 0,
awards: 0,
@ -343,7 +389,7 @@ function activateRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
function listRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Query {
sort?: string
requiresApproval?: boolean
registration?: GroupRegistrationType
continuation?: string
}
@ -356,6 +402,10 @@ function listRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
type: 'string',
enum: ['name', 'members', 'points'],
},
registration: {
type: 'string',
enum: ['open', 'approval', 'closed'],
},
continuation: { type: 'string' },
},
},
@ -377,16 +427,16 @@ function listRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
server.get<Query, DefaultParams, DefaultHeaders, DefaultBody>('/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<GroupListing>(
`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<Server, IncomingMessage, ServerRespon
}
const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
availabilityRoute(server)
createRoute(server)
getRoute(server)
blockRoute(server)

8
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<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
const database: Database = {
client: new CosmosClient({
@ -71,6 +70,7 @@ const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = a
server.register(authentication)
server.register(groups)
server.register(posts)
server.register(uploads)
server.register(users)
}

2
src/plugins/api/posts.ts

@ -29,7 +29,7 @@ import {
Status,
} from '../../types/collections'
interface PluginOptions {}
import { PluginOptions } from '../../types'
function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Body {

49
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<Server, IncomingMessage, ServerResponse>) {
const options: RouteShorthandOptions = {
schema: {
response: {
200: {
type: 'object',
properties: {
sas: { type: 'string' },
},
},
},
},
}
server.post<DefaultQuery, DefaultParams, DefaultHeaders, DefaultBody>('/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<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
getSASRoute(server)
}
export default plugin

63
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<Server, IncomingMessage, ServerResponse>) {
interface Body {
name: string
}
function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
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<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/api/user/available', options, async (request, reply) => {
if (!server.database) return serverError(reply)
const id = normalize(request.body.name)
const user = await getItem<User>({
container: containerFor(server.database.client, 'Users'),
id,
logger: request.log,
})
return {
id,
available: !!user,
}
})
}
function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Body {
name?: string
about?: string
@ -33,12 +77,6 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const options: RouteShorthandOptions = {
schema: {
headers: {
type: 'object',
properties: {
authorization: { type: 'string' },
},
},
body: {
type: 'object',
properties: {
@ -61,7 +99,7 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
},
}
server.put<DefaultQuery, DefaultParams, Headers, Body>('/api/self', options, async (request, reply) => {
server.put<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/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<Server, IncomingMessage, ServerRes
}
const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
availabilityRoute(server)
updateRoute(server)
getRoute(server)
subscribeRoute(server)

7
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

2
src/types/index.ts

@ -24,3 +24,5 @@ export interface AwardDefinition {
cost: number
reward: number
}
export interface PluginOptions {}
Loading…
Cancel
Save