├── .codecov.yml ├── .dockerignore ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .istanbul.yml ├── .npmignore ├── .travis.yml ├── Dockerfile ├── LICENSE.md ├── README.md ├── config ├── default-docker.json ├── default.json ├── local-staging-docker.json ├── local.example.json ├── production.json ├── test-docker.json └── test.json ├── data └── .gitkeep ├── docker-compose.override.yml ├── docker-compose.staging.yml ├── docker-compose.yml ├── email-templates ├── account │ ├── email-verified │ │ ├── de │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ │ └── en │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ ├── identity-change │ │ ├── de │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ │ └── en │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ ├── invite-email │ │ ├── de │ │ │ ├── html.hbs │ │ │ ├── html.hbs.json │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ │ └── en │ │ │ ├── html.hbs │ │ │ ├── html.hbs.json │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ ├── password-change │ │ ├── de │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ │ └── en │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ ├── password-reset │ │ ├── de │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ │ └── en │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ ├── reset-password │ │ ├── de │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ │ └── en │ │ │ ├── html.hbs │ │ │ ├── style.scss │ │ │ ├── subject.hbs │ │ │ └── text.hbs │ └── verify-email │ │ ├── de │ │ ├── html.hbs │ │ ├── style.scss │ │ ├── subject.hbs │ │ └── text.hbs │ │ └── en │ │ ├── html.hbs │ │ ├── style.scss │ │ ├── subject.hbs │ │ └── text.hbs └── layout │ ├── _variables.scss │ ├── common.scss │ ├── footer.hbs │ └── header.hbs ├── features ├── api │ ├── post.feature │ ├── usersettings.feature │ └── usersettings │ │ ├── blacklist.feature │ │ └── language.feature ├── env │ ├── database.js │ ├── io.js │ └── timeout.js └── step_definitions │ └── steps.js ├── package.json ├── public ├── favicon.ico └── img │ ├── badges │ ├── fundraisingbox_de_airship.svg │ ├── fundraisingbox_de_alienship.svg │ ├── fundraisingbox_de_balloon.svg │ ├── fundraisingbox_de_bigballoon.svg │ ├── fundraisingbox_de_crane.svg │ ├── fundraisingbox_de_glider.svg │ ├── fundraisingbox_de_helicopter.svg │ ├── fundraisingbox_de_starter.svg │ ├── indiegogo_en_bear.svg │ ├── indiegogo_en_panda.svg │ ├── indiegogo_en_rabbit.svg │ ├── indiegogo_en_racoon.svg │ ├── indiegogo_en_rhino.svg │ ├── indiegogo_en_tiger.svg │ ├── indiegogo_en_turtle.svg │ ├── indiegogo_en_whale.svg │ ├── indiegogo_en_wolf.svg │ ├── user_role_admin.svg │ ├── user_role_developer.svg │ ├── user_role_moderator.svg │ ├── wooold_de_bee.svg │ ├── wooold_de_butterfly.svg │ ├── wooold_de_double_rainbow.svg │ ├── wooold_de_end_of_rainbow.svg │ ├── wooold_de_flower.svg │ ├── wooold_de_lifetree.svg │ ├── wooold_de_magic_rainbow.svg │ └── wooold_de_super_founder.svg │ └── emails │ ├── alpha-ticket.png │ └── hc-logo.png ├── scripts ├── .gitignore ├── README.md └── remote-dump.sh ├── server ├── app.hooks.js ├── app.js ├── authentication.js ├── channels.js ├── helper │ ├── alter-items.js │ ├── get-mentions.js │ ├── get-unique-slug.js │ ├── seed-helpers.js │ ├── thumbor-helper.js │ └── urls.js ├── hooks │ ├── cleanup-related-items.js │ ├── conceal-blacklisted-data.js │ ├── create-default-avatar.js │ ├── create-excerpt.js │ ├── create-slug.js │ ├── exclude-blacklisted.js │ ├── exclude-disabled.js │ ├── include-all.js │ ├── is-admin.js │ ├── is-enabled.js │ ├── is-moderator-boolean.js │ ├── is-moderator.js │ ├── is-single-item.js │ ├── keep-deleted-data-fields.js │ ├── logger.js │ ├── map-create-to-upsert.js │ ├── patch-deleted-data.js │ ├── restrictReviewAndEnableChange.js │ ├── restrictToOwnerOrModerator.js │ ├── save-remote-images.js │ ├── thumbnails.js │ └── xss.js ├── index.js ├── logger.js ├── middleware │ └── index.js ├── models │ ├── badges.model.js │ ├── categories.model.js │ ├── comments.model.js │ ├── contributions.model.js │ ├── emotions.model.js │ ├── follows.model.js │ ├── images.model.js │ ├── invites.model.js │ ├── notifications.model.js │ ├── organizations.model.js │ ├── pages.model.js │ ├── projects.model.js │ ├── settings.model.js │ ├── shouts.model.js │ ├── status.model.js │ ├── system-notifications.model.js │ ├── users-candos.model.js │ ├── users.model.js │ └── usersettings.model.js ├── mongoose.js ├── seeder │ ├── base │ │ ├── badges.js │ │ ├── categories.js │ │ ├── index.js │ │ └── pages.js │ ├── demo │ │ ├── contributions.js │ │ ├── data │ │ │ ├── de_contributions.json │ │ │ └── en_contributions.json │ │ └── index.js │ ├── development │ │ ├── candos.js │ │ ├── comments.js │ │ ├── contributions.js │ │ ├── emotions.js │ │ ├── follows.js │ │ ├── index.js │ │ ├── invites.js │ │ ├── moderators.js │ │ ├── organizations.js │ │ ├── projects.js │ │ ├── shouts.js │ │ ├── users-admin.js │ │ ├── users-candos.js │ │ ├── users.js │ │ └── usersettings.js │ └── index.js └── services │ ├── admin │ ├── admin.class.js │ ├── admin.hooks.js │ └── admin.service.js │ ├── auth-management │ ├── auth-management.hooks.js │ ├── auth-management.service.js │ └── notifier.js │ ├── badges │ ├── badges.hooks.js │ └── badges.service.js │ ├── categories │ ├── categories.hooks.js │ └── categories.service.js │ ├── comments │ ├── comments.hooks.js │ ├── comments.service.js │ └── hooks │ │ ├── create-mention-notifications.js │ │ └── create-notifications.js │ ├── contributions │ ├── contributions.hooks.js │ ├── contributions.service.js │ └── hooks │ │ ├── create-mention-notifications.js │ │ ├── get-associated-can-dos.js │ │ └── notify-followers.js │ ├── emails │ ├── emails.hooks.js │ └── emails.service.js │ ├── emotions │ ├── emotions.hooks.js │ ├── emotions.service.js │ └── hooks │ │ └── emotion-rating.js │ ├── follows │ ├── follows.hooks.js │ ├── follows.service.js │ └── hooks │ │ └── set-follow-count.js │ ├── images │ ├── hooks │ │ └── upload-file.js │ ├── images.hooks.js │ └── images.service.js │ ├── index.js │ ├── invites │ ├── hooks │ │ └── send-invite-email.js │ ├── invites.hooks.js │ └── invites.service.js │ ├── notifications │ ├── notifications.hooks.js │ └── notifications.service.js │ ├── organizations │ ├── hooks │ │ ├── can-edit-organization.js │ │ └── save-avatar.js │ ├── organizations.hooks.js │ └── organizations.service.js │ ├── pages │ ├── pages.hooks.js │ └── pages.service.js │ ├── projects │ ├── projects.hooks.js │ └── projects.service.js │ ├── search │ ├── search.class.js │ ├── search.hooks.js │ └── search.service.js │ ├── settings │ ├── settings.hooks.js │ └── settings.service.js │ ├── shouts │ ├── hooks │ │ └── set-shout-count.js │ ├── shouts.hooks.js │ └── shouts.service.js │ ├── status │ ├── status.class.js │ ├── status.hooks.js │ └── status.service.js │ ├── system-notifications │ ├── system-notifications.hooks.js │ └── system-notifications.service.js │ ├── uploads │ ├── hooks │ │ └── encode.js │ ├── uploads.hooks.js │ └── uploads.service.js │ ├── user-invites │ ├── user-invites.class.js │ ├── user-invites.hooks.js │ └── user-invites.service.js │ ├── users-candos │ ├── hooks │ │ └── set-done-date.js │ ├── users-candos.hooks.js │ └── users-candos.service.js │ ├── users │ ├── hooks │ │ ├── create-admin.js │ │ ├── invite-code.js │ │ ├── is-own-entry.js │ │ ├── remove-all-related-user-data.js │ │ ├── restrict-user-role.js │ │ ├── save-avatar.js │ │ └── send-verification-email.js │ ├── users.hooks.js │ └── users.service.js │ └── usersettings │ ├── hooks │ └── validate-blacklist.js │ ├── usersettings.hooks.js │ └── usersettings.service.js ├── test ├── app.test.js.bak ├── assets │ ├── categories.js │ ├── comments.js │ ├── contributions.js │ └── users.js ├── hooks │ ├── calculate-emiotion-rating.test.js │ ├── create-default-avatar.test.js │ ├── exclude-blacklisted.test.js │ └── save-remote-images.test.js └── services │ ├── admin.test.js │ ├── auth-management.test.js │ ├── authentication.js │ ├── badges.test.js │ ├── categories.test.js │ ├── comments.test.js │ ├── contributions.test.js │ ├── emails.test.js │ ├── emotions.test.js │ ├── follows.test.js │ ├── images.test.js │ ├── invites.test.js │ ├── notifications.test.js │ ├── organizations.test.js │ ├── projects.test.js │ ├── search.test.js │ ├── settings.test.js │ ├── shouts.test.js │ ├── status.test.js │ ├── uploads.test.js │ ├── user-invites.test.js │ ├── users-candos.test.js │ ├── users.test.js │ └── usersettings.test.js └── yarn.lock /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "60...100" 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | scripts/ 4 | Dockerfile 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "es6": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "indent": [ 11 | "error", 12 | 2 13 | ], 14 | "quotes": [ 15 | "error", 16 | "single" 17 | ], 18 | "semi": [ 19 | "error", 20 | "always" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # These files are text and should be normalized (Convert crlf => lf) 2 | *.php text 3 | *.css text 4 | *.js text 5 | *.htm text 6 | *.html text 7 | *.xml text 8 | *.txt text 9 | *.ini text 10 | *.inc text 11 | .htaccess text 12 | 13 | # These files are binary and should be left untouched 14 | # (binary is a macro for -text -diff) 15 | *.png binary 16 | *.jpg binary 17 | *.jpeg binary 18 | *.gif binary 19 | *.ico binary 20 | *.mov binary 21 | *.mp4 binary 22 | *.mp3 binary 23 | *.flv binary 24 | *.fla binary 25 | *.swf binary 26 | *.gz binary 27 | *.zip binary 28 | *.7z binary 29 | *.ttf binary 30 | 31 | # Auto detect text files and perform LF normalization 32 | # http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ 33 | * text=auto 34 | 35 | # Documents 36 | *.doc diff=astextplain 37 | *.DOC diff=astextplain 38 | *.docx diff=astextplain 39 | *.DOCX diff=astextplain 40 | *.dot diff=astextplain 41 | *.DOT diff=astextplain 42 | *.pdf diff=astextplain 43 | *.PDF diff=astextplain 44 | *.rtf diff=astextplain 45 | *.RTF diff=astextplain 46 | 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Authenticate '...' 13 | 2. Post following data to endpoint '...' 14 | 3. See error 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | verbose: false 2 | instrumentation: 3 | root: ./ 4 | reporting: 5 | print: summary 6 | reports: 7 | - html 8 | - text 9 | - lcov 10 | watermarks: 11 | statements: [70, 90] 12 | lines: [70, 90] 13 | functions: [70, 90] 14 | branches: [70, 90] 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | data/ 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: 5 | yarn: true 6 | directories: 7 | - node_modules 8 | services: 9 | # we need docker for building the image and mongo for testing 10 | - docker 11 | - mongodb 12 | # install: 13 | # nothing! 14 | 15 | jobs: 16 | include: 17 | - stage: Prepare Cache 18 | script: true 19 | - stage: Build and Test 20 | script: 21 | - docker build -t humanconnection/api-feathers . 22 | - script: 23 | - yarn install --frozen-lockfile --non-interactive 24 | - yarn global add codecov 25 | - yarn run ci && codecov 26 | 27 | after_success: 28 | - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh 29 | - chmod +x send.sh 30 | - ./send.sh success $WEBHOOK_URL 31 | - if [ $TRAVIS_BRANCH == "master" ] && [ $TRAVIS_EVENT_TYPE == "push" ]; then 32 | docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD"; 33 | docker tag humanconnection/api-feathers humanconnection/api-feathers:alpha; 34 | docker push humanconnection/api-feathers:alpha; 35 | fi 36 | - if [ $TRAVIS_BRANCH == "develop" ] && [ $TRAVIS_EVENT_TYPE == "push" ]; then 37 | docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD"; 38 | docker tag humanconnection/api-feathers humanconnection/api-feathers:edge; 39 | docker push humanconnection/api-feathers:edge; 40 | fi 41 | 42 | after_failure: 43 | - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh 44 | - chmod +x send.sh 45 | - ./send.sh failure $WEBHOOK_URL 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-alpine 2 | LABEL Description="This image is used to start the hc-api-feathers" Vendor="Human-Connection gGmbH" Version="1.0" Maintainer="Human-Connection gGmbH (developer@human-connection.org)" 3 | 4 | # expose the app port 5 | EXPOSE 3030 6 | 7 | # override configuration by instance name in docker container 8 | ENV NODE_APP_INSTANCE=docker 9 | ENV NODE_ENV=production 10 | 11 | # create working directory 12 | RUN mkdir -p /API 13 | WORKDIR /API 14 | 15 | # --no-cache: download package index on-the-fly, no need to cleanup afterwards 16 | # --virtual: bundle packages, remove whole bundle at once, when done 17 | RUN apk --no-cache --virtual build-dependencies add python make g++ 18 | 19 | RUN yarn global add pm2 20 | 21 | # install app dependencies 22 | COPY package.json /API 23 | COPY yarn.lock /API 24 | RUN yarn install --production=false --frozen-lockfile --non-interactive 25 | 26 | RUN apk del build-dependencies 27 | 28 | 29 | # copy the code to the docker image 30 | COPY . /API 31 | 32 | # start the application in a autohealing cluster 33 | # NOTE: quick fix for server issues, restart api when reaching max of 300 MB Memory Usage (happens in conjunction with 100% CPU Usage) 34 | # TODO: find better way of dealing with that issue 35 | CMD ["pm2", "start", "server/index.js", "-n", "api", "--attach", "--max-memory-restart", "1024M"] 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Human-Connection gGmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Human Connection 3 |

4 | 5 | 6 |

7 | Build Status 8 | 9 | 10 | 11 | 12 | 13 | 14 | Discord 15 | 16 |

17 | 18 | # Human-Connection API 19 | This is the backend of HC.
20 | It uses the FeathersJS, NodeJS, yarn and mongoDB.
21 | 22 | ## Help 23 | The HC platform and its setup is documented in our [docs](https://docs.human-connection.org/) (work in progress).
24 | Connect with other developers over [Discord](https://discord.gg/6ub73U3)
25 | 26 | ## License 27 | Copyright (c) 2018 [Human-Connection.org](https://human-connection.org)
28 | Licensed under the [MIT](https://github.com/Human-Connection/WebApp/blob/develop/LICENSE.md) license.
29 | -------------------------------------------------------------------------------- /config/default-docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseURL": "http://api.127.0.0.1.xip.io:3030", 3 | "mongodb": "mongodb://mongo:27017/hc_api", 4 | "host": "0.0.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "localhost", 3 | "port": 3030, 4 | "baseURL": "http://localhost:3030", 5 | "frontURL": "http://localhost:3000", 6 | "public": "../public/", 7 | "debug": true, 8 | "logLevel": "log", 9 | "paginate": { 10 | "default": 10, 11 | "max": 100 12 | }, 13 | "thumbor": { 14 | "url": false, 15 | "key": false 16 | }, 17 | "apiSecret": "TE9TqAk2xK[9EFJL", 18 | "sentry": {}, 19 | "seeder": { 20 | "runOnInit": false, 21 | "dropDatabase": false 22 | }, 23 | "defaultEmail": "no-reply@human-connection.org", 24 | "smtpConfig": { 25 | "host": "0.0.0.0", 26 | "port": 1025, 27 | "ignoreTLS": true 28 | }, 29 | "mongodb": "mongodb://localhost:27017/hc_api", 30 | "authentication": { 31 | "secret": "8063adf5c0c90f12a2bbae72dc331fa0f1b4e3531980663ac7695bf108171285dc8a1ba8eaacffa11d58212301ddeae910d8ecb477782cab7d33a35b7b6c7355977da35d5fa68fa2dbc28105753629d97714d91a1163122c38850582fb0a2c774e608184dc7d7c508423870b214935046b2421b5bc60124a15d2f2df415d43c87be402a3f98bad15d78a5ca6b303243314e847d86bdb2027aa2bea0d06449cff7cef53a131bac08480fe79095a2700fae88d1454de61b87bad2449cf485a9bdecd16fc98a7defb96220f1ccd2a80f0c5838152971858338fb17f6d4b6e348992c7c838a55f3f9f2dce5c85911cb4f1b561e82ef3984dc51b3baf10ba33688a70", 32 | "strategies": [ 33 | "jwt", 34 | "local" 35 | ], 36 | "path": "/authentication", 37 | "service": "users", 38 | "session": false, 39 | "jwt": { 40 | "header": { 41 | "type": "access" 42 | }, 43 | "audience": "http://localhost", 44 | "subject": "anonymous", 45 | "issuer": "feathers", 46 | "algorithm": "HS256", 47 | "expiresIn": "1d" 48 | }, 49 | "local": { 50 | "entity": "user", 51 | "service": "users", 52 | "usernameField": "email", 53 | "passwordField": "password" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/local-staging-docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "seeder": { 3 | "runOnInit": true, 4 | "dropDatabase": false 5 | }, 6 | "smtpConfig": { 7 | "host": "maildev", 8 | "port": 25, 9 | "ignoreTLS": true 10 | }, 11 | "thumbor": { 12 | "url": "http://thumbor.127.0.0.1.xip.io:8000", 13 | "key": "" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /config/local.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "seeder": { 3 | "runOnInit": true, 4 | "dropDatabase": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "API_HOST", 3 | "port": "API_PORT", 4 | "baseURL": "API_BASE_URL", 5 | "frontURL": "WEBAPP_BASE_URL", 6 | "mongodb": "MONGO_DB", 7 | "debug": true, 8 | "logLevel": "warn", 9 | "smtpConfig": { 10 | "host": "SMTP_HOST", 11 | "port": 587, 12 | "secure": false, 13 | "auth": { 14 | "user": "SMTP_USER", 15 | "pass": "SMTP_PASS" 16 | } 17 | }, 18 | "thumbor": { 19 | "url": "THUMBOR_URL", 20 | "key": "THUMBOR_KEY" 21 | }, 22 | "sentry": { 23 | "dns": "SENTRY_DNS", 24 | "options": { 25 | "environment": "production" 26 | } 27 | }, 28 | "apiSecret": "API_SECRET", 29 | "authentication": { 30 | "secret": "AUTH_SECRET", 31 | "jwt": { 32 | "audience": "WEBAPP_BASE_URL" 33 | } 34 | }, 35 | "seeder": { 36 | "runOnInit": false, 37 | "dropDatabase": false 38 | }, 39 | "defaultEmail": "EMAIL_ADDRESS" 40 | } 41 | -------------------------------------------------------------------------------- /config/test-docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongodb": "mongodb://mongo:27017/hc_api_test" 3 | } 4 | -------------------------------------------------------------------------------- /config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 3031, 3 | "baseURL": "http://localhost:3031", 4 | "mongodb": "mongodb://localhost:27017/hc_api_test" 5 | } 6 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Human-Connection/API/8f0edaff90da046bd0db34bd77f0ead7ef336d85/data/.gitkeep -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | api: 5 | environment: 6 | - NODE_ENV=development 7 | ports: 8 | - "3030:3030" 9 | - "9229:9229" # node inspect 10 | volumes: 11 | - .:/API 12 | - /API/node_modules 13 | command: yarn run dev 14 | tty: true 15 | -------------------------------------------------------------------------------- /docker-compose.staging.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | api: 5 | image: humanconnection/api-feathers:edge 6 | build: 7 | context: . 8 | environment: 9 | - NODE_ENV=staging 10 | depends_on: 11 | - maildev 12 | - thumbor 13 | ports: 14 | - "3030:3030" 15 | networks: 16 | - hc-network 17 | 18 | maildev: 19 | image: djfarrelly/maildev 20 | networks: 21 | - hc-network 22 | ports: 23 | - "1080:80" 24 | - "1025:25" 25 | 26 | thumbor: 27 | container_name: thumbor.127.0.0.1.xip.io 28 | image: apsl/thumbor 29 | networks: 30 | - hc-network 31 | ports: 32 | - "8000:8000" 33 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | api: 5 | container_name: api.127.0.0.1.xip.io 6 | build: . 7 | depends_on: 8 | - mongo 9 | networks: 10 | - hc-network 11 | 12 | mongo: 13 | image: mongo 14 | networks: 15 | - hc-network 16 | command: "--smallfiles --logpath=/dev/null" 17 | 18 | # dns-proxy-server: 19 | # image: defreitas/dns-proxy-server 20 | # volumes: 21 | # - "/var/run/docker.sock:/var/run/docker.sock" 22 | # - "/etc/resolv.conf:/etc/resolv.conf" 23 | # - "./dns-proxy-server.config.json:/app/conf/config.json" 24 | # ports: 25 | # - 5380:5380 26 | # hostname: dns.mageddo 27 | # networks: 28 | # - hc-network 29 | 30 | networks: 31 | hc-network: 32 | name: hc-network 33 | -------------------------------------------------------------------------------- /email-templates/account/email-verified/de/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 |
8 |

Thanks for verifying your email address

9 |
13 | Hi {{ name }}, your email address has been verified. 14 |
18 | You can now contribute to our great community and get inspired to take action. Have fun! 19 |
23 | 24 |
28 | — Human Connection 29 |
32 | 33 | 34 | {{> footer }} -------------------------------------------------------------------------------- /email-templates/account/email-verified/de/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; 2 | -------------------------------------------------------------------------------- /email-templates/account/email-verified/de/subject.hbs: -------------------------------------------------------------------------------- 1 | Your email address has been verified -------------------------------------------------------------------------------- /email-templates/account/email-verified/de/text.hbs: -------------------------------------------------------------------------------- 1 | Thanks for verifying your email address 2 | 3 | Hi {{ name }}, your email address has been verified. 4 | 5 | You can now contribute to our great community and get inspired to take action. Have fun! 6 | 7 | Start exploring: {{frontURL}} 8 | 9 | — Human Connection 10 | -------------------------------------------------------------------------------- /email-templates/account/email-verified/en/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 |
8 |

Thanks for verifying your email address

9 |
13 | Hi {{ name }}, your email address has been verified. 14 |
18 | You can now contribute to our great community and get inspired to take action. Have fun! 19 |
23 | 24 |
28 | — Human Connection 29 |
32 | 33 | 34 | {{> footer }} -------------------------------------------------------------------------------- /email-templates/account/email-verified/en/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; 2 | -------------------------------------------------------------------------------- /email-templates/account/email-verified/en/subject.hbs: -------------------------------------------------------------------------------- 1 | Your email address has been verified -------------------------------------------------------------------------------- /email-templates/account/email-verified/en/text.hbs: -------------------------------------------------------------------------------- 1 | Thanks for verifying your email address 2 | 3 | Hi {{ name }}, your email address has been verified. 4 | 5 | You can now contribute to our great community and get inspired to take action. Have fun! 6 | 7 | Start exploring: {{frontURL}} 8 | 9 | — Human Connection 10 | -------------------------------------------------------------------------------- /email-templates/account/identity-change/de/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 24 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 36 | 39 | 40 |
8 |

Bestätige deine Änderungen

9 |
13 | Hallo {{ name }}, deine Profilinformationen wurden geändert, bitte überprüfe das du diese Änderungen vorgenommen hast. 14 |
18 |
    19 | {{#each changes}} 20 |
  • {{ this.label }}: {{ this.value }}
  • 21 | {{/each}} 22 |
23 |
27 | 28 |
32 | Wenn nicht du diese Änderungen vorgenommen hast lass es uns wissen! 33 |
37 | — Human Connection 38 |
41 | 42 | 43 | {{> footer }} 44 | -------------------------------------------------------------------------------- /email-templates/account/identity-change/de/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; 2 | -------------------------------------------------------------------------------- /email-templates/account/identity-change/de/subject.hbs: -------------------------------------------------------------------------------- 1 | Bestätige deine Änderungen 2 | -------------------------------------------------------------------------------- /email-templates/account/identity-change/de/text.hbs: -------------------------------------------------------------------------------- 1 | Bestätige deine Änderungen 2 | 3 | Hallo {{ name }}, deine Profilinformationen wurden geändert, bitte überprüfe das du diese Änderungen vorgenommen hast. 4 | 5 | {{#each changes}} 6 | {{ this.label }}: {{ this.value }} 7 | {{/each}} 8 | 9 | Ich bestätige die Änderungen: {{ link }} 10 | 11 | Wenn nicht du diese Änderungen vorgenommen hast lass es uns wissen! 12 | 13 | — Human Connection 14 | -------------------------------------------------------------------------------- /email-templates/account/identity-change/en/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 24 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 36 | 39 | 40 |
8 |

Please confirm these changes

9 |
13 | Hi {{ name }}, your user information was just changed. Please verify that you made these changes. 14 |
18 |
    19 | {{#each changes}} 20 |
  • {{ this.label }}: {{ this.value }}
  • 21 | {{/each}} 22 |
23 |
27 | 28 |
32 | If you didn't request this change please let us know 33 |
37 | — Human Connection 38 |
41 | 42 | 43 | {{> footer }} -------------------------------------------------------------------------------- /email-templates/account/identity-change/en/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; 2 | -------------------------------------------------------------------------------- /email-templates/account/identity-change/en/subject.hbs: -------------------------------------------------------------------------------- 1 | Confirm Changes -------------------------------------------------------------------------------- /email-templates/account/identity-change/en/text.hbs: -------------------------------------------------------------------------------- 1 | Please confirm these changes 2 | 3 | Hi {{ name }}, your user information was just changed. Please verify that you made these changes. 4 | 5 | {{#each changes}} 6 | {{ this.label }}: {{ this.value }} 7 | {{/each}} 8 | 9 | I accept the changes: {{ link }} 10 | 11 | If you didn't request this change please let us know! 12 | 13 | — Human Connection -------------------------------------------------------------------------------- /email-templates/account/invite-email/de/html.hbs.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "Hsd6ag", 3 | "email": "test@test.de", 4 | "language": "de", 5 | "frontURL": "http://localhost:3000", 6 | "backURL": "http://localhost:3030" 7 | } 8 | -------------------------------------------------------------------------------- /email-templates/account/invite-email/de/subject.hbs: -------------------------------------------------------------------------------- 1 | Dein HC Alpha Ticket 2 | -------------------------------------------------------------------------------- /email-templates/account/invite-email/en/html.hbs.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "Hsd6ag", 3 | "email": "test@test.de", 4 | "language": "en", 5 | "frontURL": "http://localhost:3000", 6 | "backURL": "http://localhost:3030" 7 | } 8 | -------------------------------------------------------------------------------- /email-templates/account/invite-email/en/subject.hbs: -------------------------------------------------------------------------------- 1 | Your HC Alpha Ticket 2 | -------------------------------------------------------------------------------- /email-templates/account/password-change/de/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 |
8 |

Passwort geändert

9 |
13 | Hallo {{ name }}, dein passwort wurde eben geändert. 14 |
18 | Wenn das nicht du warst lass es uns wissen! 19 |
23 | — Human Connection 24 |
27 | 28 | 29 | {{> footer }} 30 | -------------------------------------------------------------------------------- /email-templates/account/password-change/de/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; 2 | -------------------------------------------------------------------------------- /email-templates/account/password-change/de/subject.hbs: -------------------------------------------------------------------------------- 1 | Passwort geändert 2 | -------------------------------------------------------------------------------- /email-templates/account/password-change/de/text.hbs: -------------------------------------------------------------------------------- 1 | Passwort geändert 2 | 3 | Hallo {{ name }}, dein passwort wurde eben geändert. 4 | 5 | Wenn das nicht du warst lass es uns wissen! 6 | 7 | — Human Connection 8 | -------------------------------------------------------------------------------- /email-templates/account/password-change/en/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 |
8 |

Password change

9 |
13 | Hi {{ name }}, your password was recently changed. 14 |
18 | If you didn't request this change please let us know 19 |
23 | — Human Connection 24 |
27 | 28 | 29 | {{> footer }} -------------------------------------------------------------------------------- /email-templates/account/password-change/en/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; 2 | -------------------------------------------------------------------------------- /email-templates/account/password-change/en/subject.hbs: -------------------------------------------------------------------------------- 1 | Password Change -------------------------------------------------------------------------------- /email-templates/account/password-change/en/text.hbs: -------------------------------------------------------------------------------- 1 | Password change 2 | 3 | Hi {{ name }}, your password was recently changed. 4 | 5 | If you didn't request this change please let us know! 6 | 7 | — Human Connection -------------------------------------------------------------------------------- /email-templates/account/password-reset/de/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 |
8 |

Passwort erfolgreich zurückgesetzt

9 |
13 | Hallo {{ name }}, dein passwort wurde erfolgreich zurückgesetzt. 14 |
18 | Wenn du es nicht warst der dein Passwort zurücksetzen wollte lass es uns wissen! 19 |
23 | — Human Connection 24 |
27 | 28 | 29 | {{> footer }} 30 | -------------------------------------------------------------------------------- /email-templates/account/password-reset/de/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; -------------------------------------------------------------------------------- /email-templates/account/password-reset/de/subject.hbs: -------------------------------------------------------------------------------- 1 | Passwort erfolgreich zurückgesetzt 2 | -------------------------------------------------------------------------------- /email-templates/account/password-reset/de/text.hbs: -------------------------------------------------------------------------------- 1 | Passwort erfolgreich zurückgesetzt 2 | 3 | Hallo {{ name }}, dein passwort wurde erfolgreich zurückgesetzt. 4 | 5 | Wenn du es nicht warst der dein Passwort zurücksetzen wollte lass es uns wissen! 6 | 7 | — Human Connection 8 | -------------------------------------------------------------------------------- /email-templates/account/password-reset/en/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 |
8 |

Password reset

9 |
13 | Hi {{ name }}, your password was just reset. 14 |
18 | If you didn't request this change please let us know 19 |
23 | — Human Connection 24 |
27 | 28 | 29 | {{> footer }} -------------------------------------------------------------------------------- /email-templates/account/password-reset/en/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; -------------------------------------------------------------------------------- /email-templates/account/password-reset/en/subject.hbs: -------------------------------------------------------------------------------- 1 | Password Reset -------------------------------------------------------------------------------- /email-templates/account/password-reset/en/text.hbs: -------------------------------------------------------------------------------- 1 | Password reset 2 | 3 | Hi {{ name }}, your password was just reset. 4 | 5 | If you didn't request this change please let us know! 6 | 7 | — Human Connection -------------------------------------------------------------------------------- /email-templates/account/reset-password/de/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 | 32 | 35 | 36 |
8 |

Passwort zurücksetzen

9 |
13 | Hallo {{ name }}, wir haben die Anfrage erhalten das du dein Passwort zurücksetzen möchtest. 14 |
18 | 19 |
23 | Wenn du diese Nachricht ignorierst, bleibt dein Passwort wie es ist. 24 |
28 | Wenn du es nicht warst, der dein Passwort zurücksetzen wollte, lass es uns wissen! 29 |
33 | — Human Connection 34 |
37 | 38 | 39 | {{> footer }} 40 | -------------------------------------------------------------------------------- /email-templates/account/reset-password/de/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; -------------------------------------------------------------------------------- /email-templates/account/reset-password/de/subject.hbs: -------------------------------------------------------------------------------- 1 | Passwort zurücksetzen 2 | -------------------------------------------------------------------------------- /email-templates/account/reset-password/de/text.hbs: -------------------------------------------------------------------------------- 1 | Passwort zurücksetzen 2 | 3 | Hallo {{ name }}, wir haben die Anfrage erhalten das du dein Passwort zurücksetzen möchtest. 4 | 5 | Passwort zurücksetzen: {{ link }} 6 | 7 | Wenn du diese Nachricht ignorierst bleibt dein passwort wie es ist. 8 | 9 | Wenn du es nicht warst der dein Passwort zurücksetzen wollte lass es uns wissen! 10 | 11 | — Human Connection 12 | -------------------------------------------------------------------------------- /email-templates/account/reset-password/en/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 | 32 | 35 | 36 |
8 |

Confirm password reset

9 |
13 | Hi {{ name }}, we got a request to reset your password. 14 |
18 | 19 |
23 | If you ignore this message your password won't be changed. 24 |
28 | If you didn't request this change please let us know 29 |
33 | — Human Connection 34 |
37 | 38 | 39 | {{> footer }} -------------------------------------------------------------------------------- /email-templates/account/reset-password/en/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; -------------------------------------------------------------------------------- /email-templates/account/reset-password/en/subject.hbs: -------------------------------------------------------------------------------- 1 | Confirm Reset Password -------------------------------------------------------------------------------- /email-templates/account/reset-password/en/text.hbs: -------------------------------------------------------------------------------- 1 | Confirm password reset 2 | 3 | Hi {{ name }}, we got a request to reset your password. 4 | 5 | Reset password: {{ link }} 6 | 7 | If you ignore this message your password won't be changed. 8 | 9 | If you didn't request this change please let us know! 10 | 11 | — Human Connection -------------------------------------------------------------------------------- /email-templates/account/verify-email/de/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 |
8 |

Bestätige deine E-Mail-Adresse

9 |
13 | Hallo {{ name }}, bitte bestädige deine E-Mail-Adresse durch klick auf den unteren Link. 14 |
18 | Es ist wichtig das wir deine Korrekte E-Mail-Adresse um dein Konto zu schützen 19 |
23 | 24 |
28 | — Human Connection 29 |
32 | 33 | 34 | {{> footer }} 35 | -------------------------------------------------------------------------------- /email-templates/account/verify-email/de/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; -------------------------------------------------------------------------------- /email-templates/account/verify-email/de/subject.hbs: -------------------------------------------------------------------------------- 1 | Bestätige deine E-Mail-Adresse -------------------------------------------------------------------------------- /email-templates/account/verify-email/de/text.hbs: -------------------------------------------------------------------------------- 1 | Bitte bestätige deine E-Mail-Adresse 2 | 3 | Hallo {{ name }}, bitte bestädige deine E-Mail-Adresse durch klick auf den unteren Link. 4 | 5 | Es ist wichtig das wir deine Korrekte E-Mail-Adresse um dein Konto zu schützen. 6 | 7 | Bestätige deine E-Mail-Adresse unter der folgenden URL: 8 | {{ link }} 9 | 10 | — Human Connection 11 | -------------------------------------------------------------------------------- /email-templates/account/verify-email/en/html.hbs: -------------------------------------------------------------------------------- 1 | {{> header }} 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 |
8 |

Please confirm your email address

9 |
13 | Hi {{ name }}, please confirm your email address by clicking the link below. 14 |
18 | We may need to send you critical information about our service and it is important that we have an accurate email address. 19 |
23 | 24 |
28 | — Human Connection 29 |
32 | 33 | 34 | {{> footer }} -------------------------------------------------------------------------------- /email-templates/account/verify-email/en/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../../layout/common'; -------------------------------------------------------------------------------- /email-templates/account/verify-email/en/subject.hbs: -------------------------------------------------------------------------------- 1 | Confirm yor Email Address -------------------------------------------------------------------------------- /email-templates/account/verify-email/en/text.hbs: -------------------------------------------------------------------------------- 1 | Please confirm your email address 2 | 3 | Hi {{ name }}, please confirm your email address by clicking the link below. 4 | 5 | We may need to send you critical information about our service and it is important that we have an accurate email address. 6 | 7 | Confirm email address by following this link: 8 | {{ link }} 9 | 10 | — Human Connection 11 | -------------------------------------------------------------------------------- /email-templates/layout/_variables.scss: -------------------------------------------------------------------------------- 1 | //font-family 2 | $family-sans-serif: "Helvetica Neue", "Helvetica", "Arial", sans-serif; 3 | $body-size: 16px; 4 | 5 | //colors 6 | $black: hsl(0, 0%, 4%); 7 | $black-bis: hsl(0, 0%, 7%); 8 | $black-ter: hsl(0, 0%, 14%); 9 | 10 | $grey-darker: hsl(0, 0%, 25%); 11 | $grey-dark: hsl(0, 0%, 34%); 12 | $grey: hsl(0, 0%, 48%); 13 | $grey-light: hsl(0, 0%, 71%); 14 | $grey-lighter: hsl(0, 0%, 86%); 15 | 16 | $white-ter: hsl(0, 0%, 96%); 17 | $white-bis: hsl(0, 0%, 98%); 18 | $white: hsl(0, 0%, 100%); 19 | 20 | $orange: hsl(14, 100%, 53%); 21 | $yellow: hsl(48, 100%, 67%); 22 | $green: hsl(78, 71%, 41%); 23 | $turquoise: $green; 24 | $blue: hsl(217, 71%, 53%); 25 | $purple: hsl(271, 100%, 71%); 26 | $red: hsl(12, 51%, 55%); 27 | $primary: $green; 28 | $link: $green; 29 | 30 | $radius-small: 1px; 31 | $radius: 1px; 32 | $radius-large: 2px; 33 | 34 | $border: $grey-light; 35 | $border-hover: $grey; -------------------------------------------------------------------------------- /email-templates/layout/footer.hbs: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /email-templates/layout/header.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{title}} 7 | 8 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 |
64 |
65 | 66 | 67 | 70 | 71 |
68 | Human Connection Logo 69 |
72 |
73 | -------------------------------------------------------------------------------- /features/api/post.feature: -------------------------------------------------------------------------------- 1 | Feature: Import a post from an organization and publish it in Human Connection 2 | As a user of human connection 3 | You would like to publish a post programmatically 4 | In order to automate things 5 | 6 | 7 | Background: 8 | Given this is your user account: 9 | | email | password | 10 | | user@example.com | 1234 | 11 | 12 | Scenario: Get a JWT token 13 | When you send a POST request to "/authentication" with: 14 | """ 15 | { 16 | "email": "user@example.com", 17 | "password": "1234", 18 | "strategy": "local" 19 | } 20 | """ 21 | Then there is an access token in the response: 22 | """ 23 | { 24 | "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyIsInR5cGUiOiJhY2Nlc3MifQ..." 25 | } 26 | """ 27 | 28 | Scenario: Publish a post 29 | Given you are authenticated 30 | When you send a POST request to "/contributions" with: 31 | """ 32 | { 33 | "title": "Cool title", 34 | "content": "

A nice content

", 35 | "contentExcerpt": "Nice", 36 | "type": "post", 37 | "language": "de", 38 | "categoryIds": ["5ac7768f8d655d2ee6d48fe4"] 39 | } 40 | """ 41 | Then a new post is created 42 | -------------------------------------------------------------------------------- /features/api/usersettings.feature: -------------------------------------------------------------------------------- 1 | Feature: Save current newsfeed filters to usersettings 2 | As a user of human connection 3 | You would like to have my latest newsfeed filter settings saved 4 | In order to see the same selection of content next time I log in 5 | 6 | Background: 7 | Given this is your user account: 8 | | email | password | isVerified | 9 | | user@example.com | 1234 | true | 10 | 11 | Scenario: Save your language 12 | Given you are authenticated 13 | When you create your user settings via POST request to "/usersettings" with: 14 | """ 15 | { 16 | "uiLanguage": "de" 17 | } 18 | """ 19 | Then your language "de" is stored in your user settings 20 | 21 | Scenario: Save your filter settings 22 | Given you are authenticated 23 | When you create your user settings via POST request to "/usersettings" with: 24 | """ 25 | { 26 | "contentLanguages" : [ "en" ], 27 | "uiLanguage" : "en", 28 | "filter": { 29 | "categoryIds": [ 30 | "5b310ab8b801653c1eb6c426", 31 | "5b310ab8b801653c1eb6c427", 32 | "5b310ab8b801653c1eb6c428" 33 | ] 34 | } 35 | } 36 | """ 37 | Then these category ids are stored in your user settings 38 | -------------------------------------------------------------------------------- /features/api/usersettings/blacklist.feature: -------------------------------------------------------------------------------- 1 | Feature: Individual Blacklist 2 | As a user 3 | I want to click on a button to blacklist certain user profiles 4 | In order to stop seeing user content of this account, because I don't like them 5 | 6 | Background: 7 | Given this is your user account: 8 | | email | password | isVerified | 9 | | user@example.com | 1234 | true | 10 | And these user accounts exist: 11 | | name | email | isVerified | 12 | | Troll | troll@example.com | true | 13 | | Legit | legit@example.com | true | 14 | And you are authenticated 15 | 16 | 17 | Scenario: Blacklist a user 18 | When you create your user settings via POST request to "/usersettings" with: 19 | """ 20 | { 21 | "blacklist": ["5b5863e8d47c14323165718b"] 22 | } 23 | """ 24 | Then you will stop seeing posts of the user with id "5b5863e8d47c14323165718b" 25 | 26 | Scenario: Filter out contributions of a blacklisted user 27 | Given you blacklisted the user "Troll" before 28 | When this user publishes a post 29 | And you read your current news feed 30 | Then this post is not included 31 | 32 | Scenario: Show but conceal comments of a blacklisted user 33 | Given you blacklisted the user "Troll" before 34 | And there is a post "Hello World" by user "Legit" 35 | And the blacklisted user wrote a comment on that post: 36 | """ 37 | I hate you 38 | """ 39 | When you read through the comments of that post 40 | Then you will see a hint instead of a comment: 41 | """ 42 | Comments of this blacklisted user are not visible. 43 | """ 44 | -------------------------------------------------------------------------------- /features/api/usersettings/language.feature: -------------------------------------------------------------------------------- 1 | Feature: Save preferred language settings 2 | As a user of human connection 3 | You would like to have my preferred language saved 4 | So when I log in the next time, the UI switches to my language automatically 5 | 6 | Background: 7 | Given this is your user account: 8 | | email | password | isVerified | 9 | | user@example.com | 1234 | true | 10 | 11 | Scenario: Save your language 12 | Given you are authenticated 13 | When you create your user settings via POST request to "/usersettings" with: 14 | """ 15 | { 16 | "uiLanguage": "de" 17 | } 18 | """ 19 | Then your language "de" is stored in your user settings 20 | 21 | Scenario: Save your filter settings 22 | Given you are authenticated 23 | When you create your user settings via POST request to "/usersettings" with: 24 | """ 25 | { 26 | "contentLanguages" : [ "en" ], 27 | "uiLanguage" : "en", 28 | "filter": { 29 | "categoryIds": [ 30 | "5b310ab8b801653c1eb6c426", 31 | "5b310ab8b801653c1eb6c427", 32 | "5b310ab8b801653c1eb6c428" 33 | ] 34 | } 35 | } 36 | """ 37 | Then these category ids are stored in your user settings 38 | -------------------------------------------------------------------------------- /features/env/database.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: off */ 2 | const { Before, AfterAll, setWorldConstructor } = require('cucumber'); 3 | 4 | const backendApp = require('../../server/app'); 5 | 6 | function CustomWorld() { 7 | this.app = backendApp; 8 | } 9 | setWorldConstructor(CustomWorld); 10 | 11 | // Asynchronous Promise 12 | Before((_, callback) => { 13 | backendApp.get('mongooseClient').connection.dropDatabase().then(() => { 14 | callback(); 15 | }); 16 | }); 17 | 18 | AfterAll((callback) => { 19 | backendApp.get('mongooseClient').disconnect().then(() => { 20 | callback(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /features/env/io.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: off */ 2 | const { Before } = require('cucumber'); 3 | const fs = require('fs-extra'); 4 | 5 | const tmpDir = './tmp'; 6 | 7 | Before(() => fs.remove(tmpDir).then(() => fs.ensureDir(tmpDir))); 8 | 9 | -------------------------------------------------------------------------------- /features/env/timeout.js: -------------------------------------------------------------------------------- 1 | const { setDefaultTimeout } = require('cucumber'); 2 | 3 | setDefaultTimeout(60 * 1000); 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Human-Connection/API/8f0edaff90da046bd0db34bd77f0ead7ef336d85/public/favicon.ico -------------------------------------------------------------------------------- /public/img/badges/fundraisingbox_de_airship.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/fundraisingbox_de_balloon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/fundraisingbox_de_crane.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/fundraisingbox_de_glider.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/fundraisingbox_de_helicopter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/fundraisingbox_de_starter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/indiegogo_en_bear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/indiegogo_en_rabbit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/indiegogo_en_whale.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/badges/user_role_developer.svg: -------------------------------------------------------------------------------- 1 | </> -------------------------------------------------------------------------------- /public/img/badges/user_role_moderator.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/emails/alpha-ticket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Human-Connection/API/8f0edaff90da046bd0db34bd77f0ead7ef336d85/public/img/emails/alpha-ticket.png -------------------------------------------------------------------------------- /public/img/emails/hc-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Human-Connection/API/8f0edaff90da046bd0db34bd77f0ead7ef336d85/public/img/emails/hc-logo.png -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !remote-dump.sh 3 | !README.md 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /scripts/remote-dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for var in "SSH_USERNAME" "SSH_HOST" "MONGODB_USERNAME" "MONGODB_PASSWORD" "MONGODB_DATABASE" 4 | do 5 | if [[ -z "${!var}" ]]; then 6 | echo "${var} is undefined" 7 | exit -1 8 | fi 9 | done 10 | 11 | OUTPUT_FILE_NAME=${OUTPUT:-human-connection-dump}_$(date -I).archive 12 | 13 | echo "SSH_USERNAME ${SSH_USERNAME}" 14 | echo "SSH_HOST ${SSH_HOST}" 15 | echo "MONGODB_USERNAME ${MONGODB_USERNAME}" 16 | echo "MONGODB_PASSWORD ${MONGODB_PASSWORD}" 17 | echo "MONGODB_DATABASE ${MONGODB_DATABASE}" 18 | echo "OUTPUT_FILE_NAME ${OUTPUT_FILE_NAME}" 19 | echo "GPG_PASSWORD ${GPG_PASSWORD:-}" 20 | echo "-------------------------------------------------" 21 | 22 | ssh -M -S my-ctrl-socket -fnNT -L 27018:localhost:27017 -l ${SSH_USERNAME} ${SSH_HOST} 23 | 24 | if [[ -z "${!GPG_PASSWORD}" ]]; then 25 | mongodump --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase admin --gzip --archive | gpg -c --batch --passphrase ${GPG_PASSWORD} --output ${OUTPUT_FILE_NAME}.gpg 26 | else 27 | mongodump --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase admin --gzip --archive=${OUTPUT_FILE_NAME} 28 | fi 29 | 30 | 31 | ssh -S my-ctrl-socket -O check -l ${SSH_USERNAME} ${SSH_HOST} 32 | ssh -S my-ctrl-socket -O exit -l ${SSH_USERNAME} ${SSH_HOST} 33 | -------------------------------------------------------------------------------- /server/app.hooks.js: -------------------------------------------------------------------------------- 1 | // Application hooks that run for every service 2 | // const logger = require('./hooks/logger'); 3 | const { discard } = require('feathers-hooks-common'); 4 | 5 | module.exports = { 6 | before: { 7 | all: [], 8 | find: [], 9 | get: [], 10 | create: [ 11 | discard('_id', '__v') 12 | ], 13 | update: [ 14 | discard('_id', '__v') 15 | ], 16 | patch: [ 17 | discard('_id', '__v') 18 | ], 19 | remove: [] 20 | }, 21 | 22 | after: { 23 | all: [ 24 | // logger() 25 | ], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | }, 33 | 34 | error: { 35 | all: [ 36 | // logger() 37 | ], 38 | find: [], 39 | get: [], 40 | create: [], 41 | update: [], 42 | patch: [], 43 | remove: [] 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /server/authentication.js: -------------------------------------------------------------------------------- 1 | const authentication = require('@feathersjs/authentication'); 2 | const jwt = require('@feathersjs/authentication-jwt'); 3 | const local = require('@feathersjs/authentication-local'); 4 | const { lowerCase } = require('feathers-hooks-common'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const config = app.get('authentication'); 9 | 10 | // Set up authentication with the secret 11 | app.configure(authentication(config)); 12 | app.configure(jwt()); 13 | app.configure(local(config.local)); 14 | 15 | // The `authentication` service is used to create a JWT. 16 | // The before `create` hook registers strategies that can be used 17 | // to create a new valid JWT (e.g. local or oauth2) 18 | app.service('authentication').hooks({ 19 | before: { 20 | create: [ 21 | lowerCase('email', 'username'), 22 | authentication.hooks.authenticate(config.strategies) 23 | ], 24 | remove: [ 25 | authentication.hooks.authenticate('jwt') 26 | ] 27 | } 28 | }); 29 | 30 | app.on('login', (result, meta) => { 31 | try { 32 | if (meta.connection && meta.connection.user) { 33 | // update last active timestamp on loggedin user 34 | app.service('users').patch(meta.connection.user, { 35 | lastActiveAt: new Date() 36 | }); 37 | } 38 | } catch (err) { 39 | app.error(err); 40 | } 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /server/helper/alter-items.js: -------------------------------------------------------------------------------- 1 | const { getItems, replaceItems } = require('feathers-hooks-common'); 2 | 3 | /** 4 | * Alter items in hook.data or hook.result 5 | * 6 | * Example usage in hook module: 7 | * module.exports = (options = defaults) => alterItems(handleItem(options)); 8 | * 9 | * handleItem() is the function in which changes are made 10 | */ 11 | module.exports = func => hook => { 12 | if (!func || typeof func !== 'function') { 13 | return hook; 14 | } 15 | let items = getItems(hook); 16 | if (Array.isArray(items)) { 17 | items = items.map(item => func(item, hook)); 18 | } else if (items) { 19 | items = func(items, hook); 20 | } 21 | replaceItems(hook, items); 22 | return hook; 23 | }; 24 | -------------------------------------------------------------------------------- /server/helper/get-mentions.js: -------------------------------------------------------------------------------- 1 | const mentionRegex = /data-hc-mention="([\s\S]+?)"/g; 2 | 3 | const getMentions = async (app, content) => { 4 | let match = mentionRegex.exec(content); 5 | let mention = {}; 6 | let mentionsTable = {}; 7 | while (match !== null) { 8 | mention = JSON.parse(match[1].replace(/"/g, '"')); 9 | if (!mentionsTable[mention._id]) { 10 | let mentionData = await app.service('users').get(mention._id); 11 | mentionsTable[mention._id] = mentionData; 12 | } 13 | match = mentionRegex.exec(content); 14 | } 15 | return mentionsTable; 16 | }; 17 | 18 | module.exports = getMentions; -------------------------------------------------------------------------------- /server/helper/get-unique-slug.js: -------------------------------------------------------------------------------- 1 | const getUniqueSlug = (service, slug, count, id) => { 2 | return new Promise(resolve => { 3 | const testSlug = count ? slug + count : slug; 4 | 5 | // Test if we already have data with this slug 6 | const query = { 7 | slug: testSlug 8 | }; 9 | // ignore entry with given id (if set) 10 | if (id) { 11 | query._id = { 12 | $ne: id 13 | }; 14 | } 15 | service.find({ 16 | query, 17 | _includeAll: true, 18 | _populate: 'skip' 19 | }).then((result) => { 20 | if (result.data.length > 0) { 21 | count = count ? count + 1 : 1; 22 | resolve(getUniqueSlug(service, slug, count)); 23 | } else { 24 | resolve(testSlug); 25 | } 26 | }); 27 | }); 28 | }; 29 | 30 | module.exports = getUniqueSlug; 31 | -------------------------------------------------------------------------------- /server/helper/thumbor-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto-js'); 4 | 5 | /** 6 | * @param {[type]} securityKey 7 | * @param {[type]} thumborServerUrl 8 | */ 9 | function ThumborUrlHelper(securityKey, thumborServerUrl) { 10 | 'use strict'; 11 | 12 | this.THUMBOR_SECURITY_KEY = securityKey; 13 | this.THUMBOR_URL_SERVER = thumborServerUrl; 14 | } 15 | 16 | ThumborUrlHelper.prototype = { 17 | /** 18 | * Set path of image 19 | * @param {String} imagePath [description] 20 | */ 21 | setImagePath: function (imagePath) { 22 | this.imagePath = (imagePath.charAt(0) === '/') ? 23 | imagePath.substring(1, imagePath.length) : imagePath; 24 | return this; 25 | }, 26 | /** 27 | * Combine image url and operations with secure and unsecure (unsafe) paths 28 | * @return {String} 29 | */ 30 | buildUrl: function (operations) { 31 | 32 | if (this.THUMBOR_SECURITY_KEY) { 33 | 34 | let key = crypto.HmacSHA1(operations + '/' + this.imagePath, this.THUMBOR_SECURITY_KEY); 35 | key = crypto.enc.Base64.stringify(key); 36 | key = key.replace(/\+/g, '-').replace(/\//g, '_'); 37 | 38 | return this.THUMBOR_URL_SERVER + 39 | '/' + key + 40 | '/' + operations + '/' + 41 | this.imagePath; 42 | } else { 43 | return this.THUMBOR_URL_SERVER + '/unsafe/' + operations + '/' +this.imagePath; 44 | } 45 | } 46 | }; 47 | 48 | module.exports = ThumborUrlHelper; 49 | -------------------------------------------------------------------------------- /server/helper/urls.js: -------------------------------------------------------------------------------- 1 | import { merge } from 'lodash'; 2 | 3 | export default { 4 | buildEndpointURL (host, options = {}) { 5 | options = merge({ protocol: 'http', port: null }, options || {}); 6 | let endpoint = host; 7 | if (host.indexOf('://') < 0 && options.protocol) { 8 | endpoint = `${options.protocol}://${endpoint}`; 9 | } 10 | if (options.port > 0 && options.port !== 443 && options.port !== 80) { 11 | endpoint = `${endpoint}:${options.port}`; 12 | } 13 | return endpoint; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /server/hooks/cleanup-related-items.js: -------------------------------------------------------------------------------- 1 | // Cleanup all related items by deleting them 2 | const alterItems = require('../helper/alter-items'); 3 | 4 | const defaults = { 5 | connections: [] 6 | }; 7 | 8 | module.exports = (options = defaults) => alterItems(handleItem(options)); 9 | 10 | const handleItem = options => (item, hook) => { 11 | options.connections.forEach(connection => { 12 | deleteItem(item, connection, hook); 13 | }); 14 | return item; 15 | }; 16 | 17 | const deleteItem = async (item, connection, hook) => { 18 | if (!item || !connection || !connection.childField || !connection.service) { 19 | return; 20 | } 21 | const parentField = connection.parentField || '_id'; 22 | let query = connection.query || {}; 23 | query[connection.childField] = item[parentField]; 24 | try { 25 | await hook.app.service(connection.service) 26 | .remove(null, { query }); 27 | } catch (err) { 28 | hook.app.error(`issue while deleting related item '${connection.service}'`); 29 | hook.app.error(query); 30 | hook.app.error(err); 31 | } 32 | }; -------------------------------------------------------------------------------- /server/hooks/conceal-blacklisted-data.js: -------------------------------------------------------------------------------- 1 | const alterItems = require('../helper/alter-items'); 2 | 3 | const defaults = { 4 | blacklist: [], 5 | data: { 6 | content: 'You have blacklisted this user' 7 | } 8 | }; 9 | 10 | const handleItem = options => item => { 11 | if (options.blacklist){ 12 | let found = options.blacklist.find((userId) => { 13 | return String(userId) === item.userId; 14 | }); 15 | if (found){ 16 | item = {...item, ...options.data}; 17 | } 18 | } 19 | return item; 20 | }; 21 | 22 | module.exports = function concealBlacklistedData(options = defaults) { 23 | return async function(hook){ 24 | if (hook.method === 'find' || hook.id === null) { 25 | const authenticatedUser = hook.params.user; 26 | if (!authenticatedUser){ 27 | return hook; 28 | } 29 | const usersettings = await hook.app.service('usersettings').find({query: {userId: authenticatedUser._id}}); 30 | if (usersettings.total <= 0){ 31 | return hook; 32 | } 33 | options.blacklist = usersettings.data[0].blacklist; 34 | return alterItems(handleItem(options))(hook); 35 | } 36 | 37 | return hook; 38 | }; 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /server/hooks/create-default-avatar.js: -------------------------------------------------------------------------------- 1 | // Use this hook to manipulate incoming or outgoing data. 2 | // For more information on hooks see: http://docs.feathersjs.com/api/hooks.html 3 | 4 | const crypto = require('crypto'); 5 | 6 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 7 | return function (hook) { 8 | // Hooks can either return nothing or a promise 9 | // that resolves with the `hook` object for asynchronous operations 10 | 11 | if (hook.data && !hook.data.avatar) { 12 | const emailhash = crypto.createHmac('sha256', hook.data.email).digest('hex'); 13 | hook.data.avatar = `https://api.adorable.io/avatars/250/${emailhash}.png`; 14 | } 15 | 16 | return hook; 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /server/hooks/create-slug.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/slug 2 | const slug = require('slug'); 3 | const getUniqueSlug = require('../helper/get-unique-slug'); 4 | const { isEmpty } = require('lodash'); 5 | 6 | module.exports = function (options = {}) { 7 | return function (hook) { 8 | options = Object.assign({ field: null, overwrite: false, unique: true }, options); 9 | 10 | if (!options.field || !hook.data[options.field]) return hook; 11 | 12 | // do not overwrite existing slug 13 | // TODO: we should make that possible and relying on ids for routing instead only on slugs 14 | // the slug should be there for seo reasons but the id should be what counts 15 | if (!isEmpty(hook.data.slug) && options.overwrite !== true) return hook; 16 | 17 | return new Promise(resolve => { 18 | const titleSlug = slug(hook.data[options.field], { 19 | lower: true 20 | }); 21 | if (options.unique !== false) { 22 | getUniqueSlug(hook.service, titleSlug, null, hook.id) 23 | .then((uniqueSlug) => { 24 | hook.data.slug = uniqueSlug; 25 | resolve(hook); 26 | }); 27 | } else { 28 | hook.data.slug = titleSlug; 29 | resolve(hook); 30 | } 31 | }); 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /server/hooks/exclude-blacklisted.js: -------------------------------------------------------------------------------- 1 | module.exports = function excludeBlacklisted() { 2 | return async function (hook) { 3 | if (hook.type !== 'before') { 4 | throw new Error('The \'excludeBlacklisted\' hook should only be used as a \'before\' hook.'); 5 | } 6 | 7 | if (hook.method === 'find' || hook.id === null) { 8 | const authenticatedUser = hook.params.user; 9 | if (!authenticatedUser){ 10 | return hook; 11 | } 12 | const usersettings = await hook.app.service('usersettings').find({query: {userId: authenticatedUser._id}}); 13 | if (usersettings.total <= 0){ 14 | return hook; 15 | } 16 | const { blacklist } = usersettings.data[0]; 17 | if (blacklist) { 18 | let { query } = hook.params; 19 | query.userId = query.userId || {}; 20 | query.userId.$nin = blacklist; 21 | } 22 | return hook; 23 | } 24 | 25 | return hook; 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /server/hooks/exclude-disabled.js: -------------------------------------------------------------------------------- 1 | // Exclude items with isEnabled = false 2 | const errors = require('@feathersjs/errors'); 3 | 4 | module.exports = function excludeDisabled() { 5 | return function (hook) { 6 | if (hook.type !== 'before') { 7 | throw new Error('The \'excludeDisabled\' hook should only be used as a \'before\' hook.'); 8 | } 9 | 10 | // If it was an internal call then skip this hook 11 | if (!hook.params.provider) { 12 | return hook; 13 | } 14 | 15 | if (hook.method === 'find' || hook.id === null) { 16 | hook.params.query.isEnabled = true; 17 | return hook; 18 | } 19 | 20 | // look up the document and throw a Forbidden error if the item is not enabled 21 | // Set provider as undefined so we avoid an infinite loop if this hook is 22 | // set on the resource we are requesting. 23 | var params = Object.assign({}, hook.params, { provider: undefined }); 24 | 25 | return hook.service.get(hook.id, params).then(function (data) { 26 | if (data.toJSON) { 27 | data = data.toJSON(); 28 | } else if (data.toObject) { 29 | data = data.toObject(); 30 | } 31 | 32 | if (!data || !data.isEnabled) { 33 | throw new errors.Forbidden('This item is disabled.'); 34 | } 35 | 36 | return hook; 37 | }); 38 | }; 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /server/hooks/include-all.js: -------------------------------------------------------------------------------- 1 | module.exports = () => hook => { 2 | if (!hook.params._includeAll) { 3 | return hook; 4 | } 5 | if (hook.params.query.deleted) { 6 | delete hook.params.query.deleted; 7 | } 8 | if (hook.params.query.isEnabled) { 9 | delete hook.params.query.isEnabled; 10 | } 11 | return hook; 12 | }; 13 | -------------------------------------------------------------------------------- /server/hooks/is-admin.js: -------------------------------------------------------------------------------- 1 | // Check if user is admin 2 | const errors = require('@feathersjs/errors'); 3 | 4 | module.exports = () => hook => { 5 | if(!hook.params || !hook.params.user || hook.params.user.role !== 'admin') { 6 | throw new errors.Forbidden('You don\'t have admin rights.'); 7 | } 8 | return hook; 9 | }; 10 | -------------------------------------------------------------------------------- /server/hooks/is-enabled.js: -------------------------------------------------------------------------------- 1 | // Use this hook to manipulate incoming or outgoing data. 2 | // For more information on hooks see: http://docs.feathersjs.com/api/hooks.html 3 | 4 | const _ = require('lodash'); 5 | const errors = require('@feathersjs/errors'); 6 | 7 | module.exports = function isEnabled(options = {}) { // eslint-disable-line no-unused-vars 8 | return function (hook) { 9 | 10 | if (!hook.params.provider) { return Promise.resolve(hook); } 11 | 12 | if(_.get(hook, 'params.user.role') === 'admin') { return Promise.resolve(hook); } 13 | 14 | if(!_.get(hook, 'params.user') || _.isEmpty(hook.params.user)) { 15 | 16 | throw new errors.NotAuthenticated('Cannot check if the user is enabled. You must not be authenticated.'); 17 | 18 | } else if(!_.get(hook, 'params.user.isEnabled')) { 19 | 20 | var name = _.get(hook, 'params.user.name') || _.get(hook, 'params.user.email') || 'This user '; 21 | 22 | throw new errors.Forbidden(`${name} is disabled.`); 23 | } 24 | }; 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /server/hooks/is-moderator-boolean.js: -------------------------------------------------------------------------------- 1 | // Check if user is moderator 2 | module.exports = () => hook => { 3 | if(!hook.params || !hook.params.user) { 4 | return false; 5 | } 6 | return ['admin', 'moderator'].includes(hook.params.user.role); 7 | }; 8 | -------------------------------------------------------------------------------- /server/hooks/is-moderator.js: -------------------------------------------------------------------------------- 1 | // Check if user is moderator 2 | const errors = require('@feathersjs/errors'); 3 | 4 | module.exports = () => hook => { 5 | if(!hook.params || !hook.params.user || !['admin','moderator'].includes(hook.params.user.role)) { 6 | throw new errors.Forbidden('You don\'t have moderator rights.'); 7 | } 8 | return hook; 9 | }; 10 | -------------------------------------------------------------------------------- /server/hooks/is-single-item.js: -------------------------------------------------------------------------------- 1 | // Check if the query is for a single item 2 | module.exports = () => hook => { 3 | if (hook && hook.params && hook.params.query && (hook.params.query.$limit === 1 || hook.params.query.slug)) { 4 | return true; 5 | } else { 6 | return false; 7 | } 8 | }; -------------------------------------------------------------------------------- /server/hooks/keep-deleted-data-fields.js: -------------------------------------------------------------------------------- 1 | // Remove data from deleted items 2 | const alterItems = require('../helper/alter-items'); 3 | 4 | const defaults = { 5 | fields: [ 6 | '_id', 7 | 'deleted', 8 | 'createdAt', 9 | 'updatedAt' 10 | ] 11 | }; 12 | 13 | module.exports = (options = defaults) => alterItems(handleItem(options)); 14 | 15 | const handleItem = options => item => { 16 | if (item.deleted) { 17 | Object.keys(item).forEach(key => { 18 | if (!options.fields.includes(key)) { 19 | delete item[key]; 20 | } 21 | }); 22 | } 23 | return item; 24 | }; -------------------------------------------------------------------------------- /server/hooks/logger.js: -------------------------------------------------------------------------------- 1 | // A hook that logs service method before, after and error 2 | 3 | module.exports = function () { 4 | return function (hook) { 5 | let message = `${hook.type}: ${hook.path} - Method: ${hook.method}`; 6 | 7 | if (hook.type === 'error') { 8 | message += `: ${hook.error.message}`; 9 | } 10 | 11 | hook.app.info(message); 12 | // hook.app.debug({ 13 | // hook: 14 | // { 15 | // data: hook.data, 16 | // params: hook.params 17 | // } 18 | // }); 19 | 20 | // if (hook.result) { 21 | // hook.app.debug('hook.result', hook.result); 22 | // } 23 | 24 | // if (hook.error) { 25 | // hook.app.error(hook.error); 26 | // } 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /server/hooks/map-create-to-upsert.js: -------------------------------------------------------------------------------- 1 | // upsert if needed 2 | // https://blog.feathersjs.com/how-to-use-upsert-for-better-performance-with-feathers-mongoose-ec40e45d0d4a 3 | 4 | module.exports = function (upsertQuery) { 5 | if (typeof upsertQuery !== 'function') { 6 | throw new Error('No `upsertQuery` function was passed to the mapCreateToUpsert hook. Please set params.upsertQuery in the hook context to dynamically declare the function.'); 7 | } 8 | 9 | return function mapCreateToUpsert (context) { 10 | const { service, data, params } = context; // const data = { address: '123', identifier: 'my-identifier' } 11 | 12 | upsertQuery = params.upsertQuery || upsertQuery; 13 | if (typeof upsertQuery !== 'function') { 14 | throw new Error('you must pass a `upsertQuery` function to the mapCreateToUpsert hook in the options or as `params.upsertQuery` in the hook context'); 15 | } 16 | 17 | params.mongoose = Object.assign({}, params.mongoose, { upsert: true }); 18 | params.query = upsertQuery(context); 19 | 20 | return service.patch(null, data, params) 21 | .then(result => { 22 | context.result = result; 23 | return context; 24 | }); 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /server/hooks/patch-deleted-data.js: -------------------------------------------------------------------------------- 1 | // Patch deleted records with given data 2 | const alterItems = require('../helper/alter-items'); 3 | 4 | const defaults = { 5 | data: { 6 | content: 'DELETED' 7 | } 8 | }; 9 | 10 | module.exports = (options = defaults) => alterItems(handleItem(options)); 11 | 12 | const handleItem = options => item => { 13 | if (item.deleted) { 14 | item = {...item, ...options.data}; 15 | } 16 | return item; 17 | }; -------------------------------------------------------------------------------- /server/hooks/restrictReviewAndEnableChange.js: -------------------------------------------------------------------------------- 1 | const { getByDot, deleteByDot } = require('feathers-hooks-common'); 2 | 3 | module.exports = function restrictReviewAndEnableChange () { // eslint-disable-line no-unused-vars 4 | return (hook) => { 5 | 6 | if (!getByDot(hook, 'params.before')) { 7 | throw new Error('The "restrictReviewAndEnableChange" hook should be used after the "stashBefore()" hook'); 8 | } 9 | 10 | const role = getByDot(hook, 'params.user.role'); 11 | const isModOrAdmin = role && ['admin', 'moderator'].includes(role); 12 | const isReviewed = getByDot(hook, 'params.before.reviewedBy'); 13 | const userId = getByDot(hook, 'params.user._id'); 14 | const ownerId = getByDot(hook, 'params.before.userId'); 15 | const isOwner = userId && ownerId && ownerId.toString() === userId.toString(); 16 | 17 | // only allow mods and admins to change the review status 18 | if (!isModOrAdmin) { 19 | deleteByDot(hook.data, 'isReviewed'); 20 | } 21 | 22 | // set reviewedBy to current user if the user has mod rights 23 | // and wants to confirm the review status 24 | deleteByDot(hook.data, 'reviewedBy'); 25 | if (hook.data.isReviewed) { 26 | hook.data.reviewedBy = userId; 27 | } 28 | 29 | // only allow changes to mods, admin and owners (if its already reviewed) 30 | if (!isModOrAdmin && (!isOwner || (isOwner && !isReviewed))) { 31 | deleteByDot(hook.data, 'isEnabled'); 32 | } 33 | 34 | return hook; 35 | }; 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const app = require('./app'); 3 | const port = app.get('port'); 4 | const host = app.get('host'); 5 | 6 | process.on('unhandledRejection', function (err) { 7 | throw err; 8 | }); 9 | 10 | process.on('uncaughtException', function (err) { 11 | app.error(err); 12 | }); 13 | 14 | // Start server 15 | const server = app.listen(port, host); 16 | server.on('listening', () => { 17 | // Start seeder, after database is setup 18 | if (app.get('seeder').runOnInit === true) { 19 | app.on('mongooseInit', () => { 20 | app.service('users').find({ query: { $limit: 0 }}) 21 | .then(async (res) => { 22 | app.debug(res); 23 | if (res.total > 0) { 24 | return null; 25 | } 26 | app.info(`Feathers application started on ${app.get('host')}:${port}`); 27 | app.info('>>>>>> RUN SEEDER <<<<<<'); 28 | await app.seed(); 29 | }); 30 | }); 31 | } else { 32 | app.service('categories').find({ query: { $limit: 0 }}) 33 | .then(async (res) => { 34 | if (res.total < 1) { 35 | app.service('admin').create({ seedBaseCategories: true }); 36 | } 37 | }); 38 | app.service('badges').find({ query: { $limit: 0 }}) 39 | .then(async (res) => { 40 | if (res.total < 1) { 41 | app.service('admin').create({ seedBaseBadges: true }); 42 | } 43 | }); 44 | app.info(`Feathers application started on ${app.get('host')}:${port}`); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /server/logger.js: -------------------------------------------------------------------------------- 1 | const { createLogger, format, transports } = require('winston'); 2 | const env = process.env.NODE_ENV; 3 | 4 | const logger = createLogger({ 5 | level: env !== 'production' ? 'warn' : 'debug', 6 | format: format.json(), 7 | transports: [ 8 | new transports.File({ filename: './data/error.log', level: 'error' }), 9 | new transports.File({ filename: './data/debug.log', level: 'debug' }), 10 | new transports.File({ filename: './data/combined.log' }) 11 | ] 12 | }); 13 | logger.add(new transports.Console({ 14 | format: format.combine( 15 | format.printf((info) => { 16 | const date = new Date(); 17 | const levelColors = { 18 | info: '', 19 | debug: '\u001b[34m', 20 | warning: '\u001b[33m', 21 | error: '\u001b[31m' 22 | }; 23 | return `${levelColors[info.level]}${date.toLocaleTimeString()} | ${info.level}: ${JSON.stringify(info.message)}\u001b[39m`; 24 | }) 25 | ), 26 | level: env === 'development' ? 'debug' : 'warn' 27 | })); 28 | module.exports = logger; 29 | -------------------------------------------------------------------------------- /server/middleware/index.js: -------------------------------------------------------------------------------- 1 | const handler = require('@feathersjs/express/errors'); 2 | const notFound = require('feathers-errors/not-found'); 3 | 4 | module.exports = function () { 5 | // Add your custom middleware here. Remember, that 6 | // in Express the order matters, `notFound` and 7 | // the error handler have to go last. 8 | const app = this; 9 | 10 | app.use(notFound()); 11 | app.use(handler()); 12 | }; 13 | -------------------------------------------------------------------------------- /server/models/badges.model.js: -------------------------------------------------------------------------------- 1 | // badges-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const { Schema } = mongooseClient; 8 | 9 | // path is the filename and maybe an additional path 10 | // has to be discussed, maybe we need to switch to filename only 11 | // or add property filename to the path 12 | const imageSchema = mongooseClient.Schema({ 13 | path: {type: String, required: true}, 14 | alt: {type: String, required: true} 15 | }); 16 | const badges = new Schema({ 17 | image: imageSchema, 18 | // may be temporary or some other status 19 | status: { 20 | type: String, 21 | enum: ['permanent', 'temporary'], 22 | default: 'permanent', 23 | required: true 24 | }, 25 | // the type off the badge, e.g. 26 | // Crowdfounder badge is a one time forever badge 27 | // some badges are given got by an achivement 28 | // some badges given by events or they are a gift form an other user 29 | // some badges may be temporary like moderator badge 30 | type: { type: String, required: true }, 31 | key: { type: String, required: true }, 32 | createdAt: { type: Date, default: Date.now }, 33 | updatedAt: { type: Date, default: Date.now } 34 | }); 35 | 36 | return mongooseClient.model('badges', badges); 37 | }; 38 | -------------------------------------------------------------------------------- /server/models/categories.model.js: -------------------------------------------------------------------------------- 1 | // categories-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const categories = new mongooseClient.Schema({ 8 | title: { type: String, required: true }, 9 | // Generated from title 10 | slug: { type: String, required: true, unique: true }, 11 | icon: { type: String, unique: true }, 12 | createdAt: { type: Date, default: Date.now }, 13 | updatedAt: { type: Date, default: Date.now } 14 | }); 15 | 16 | return mongooseClient.model('categories', categories); 17 | }; 18 | -------------------------------------------------------------------------------- /server/models/comments.model.js: -------------------------------------------------------------------------------- 1 | // comments-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const comments = new mongooseClient.Schema({ 8 | userId: { type: String, required: true, index: true }, 9 | contributionId: { type: String, required: true, index: true }, 10 | content: { type: String, required: true }, 11 | // Generated from content 12 | contentExcerpt: { type: String, required: true }, 13 | hasMore: { type: Boolean }, 14 | upvotes: { type: Array, default: [] }, 15 | upvoteCount: { type: Number, default: 0 }, 16 | deleted: { type: Boolean, default: false, index: true }, 17 | createdAt: { type: Date, default: Date.now }, 18 | updatedAt: { type: Date, default: Date.now }, 19 | wasSeeded: { type: Boolean } 20 | }); 21 | 22 | return mongooseClient.model('comments', comments); 23 | }; 24 | -------------------------------------------------------------------------------- /server/models/emotions.model.js: -------------------------------------------------------------------------------- 1 | // emotions-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const emotions = new mongooseClient.Schema({ 8 | userId: { type: String, required: true, index: true }, 9 | contributionId: { type: String, required: true, index: true }, 10 | rated: { 11 | type: String, 12 | required: true, 13 | enum: ['funny', 'happy', 'surprised', 'cry', 'angry'] 14 | }, 15 | createdAt: { type: Date, default: Date.now }, 16 | updatedAt: { type: Date, default: Date.now }, 17 | wasSeeded: { type: Boolean } 18 | }); 19 | 20 | return mongooseClient.model('emotions', emotions); 21 | }; 22 | -------------------------------------------------------------------------------- /server/models/follows.model.js: -------------------------------------------------------------------------------- 1 | // users-follows-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const follows = new mongooseClient.Schema({ 8 | userId: { type: String, required: true, index: true }, 9 | foreignId: { type: String, required: true, index: true }, 10 | foreignService: { type: String, required: true, index: true }, 11 | createdAt: { type: Date, default: Date.now }, 12 | wasSeeded: { type: Boolean } 13 | }); 14 | 15 | follows.index( 16 | { userId: 1, foreignId: 1, foreignService: 1 }, 17 | { unique: true } 18 | ); 19 | 20 | return mongooseClient.model('follows', follows); 21 | }; 22 | -------------------------------------------------------------------------------- /server/models/images.model.js: -------------------------------------------------------------------------------- 1 | // images-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const images = new mongooseClient.Schema({ 8 | userId: { type: String, required: true }, 9 | title: { type: String }, 10 | // During image creation, we expect to have 11 | // uri: { type: Imagefile Data URI, required: true } 12 | fileName: { type: String, required: true }, 13 | createdAt: { type: Date, default: Date.now }, 14 | updatedAt: { type: Date, default: Date.now } 15 | }); 16 | 17 | return mongooseClient.model('images', images); 18 | }; 19 | -------------------------------------------------------------------------------- /server/models/invites.model.js: -------------------------------------------------------------------------------- 1 | // invites-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const invites = new mongooseClient.Schema({ 8 | email: { type: String, required: true, index: true, unique: true }, 9 | code: { type: String, index: true, required: true }, 10 | role: { 11 | type: String, 12 | enum: ['admin', 'moderator', 'manager', 'editor', 'user'], 13 | default: 'user' 14 | }, 15 | invitedByUserId: { type: String }, 16 | language: { type: String }, 17 | badgeIds: [], 18 | wasUsed: { type: Boolean, index: true }, 19 | createdAt: { type: Date, default: Date.now }, 20 | wasSeeded: { type: Boolean } 21 | }); 22 | 23 | return mongooseClient.model('invites', invites); 24 | }; 25 | -------------------------------------------------------------------------------- /server/models/notifications.model.js: -------------------------------------------------------------------------------- 1 | // notifications-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const notifications = new mongooseClient.Schema({ 8 | // User this notification is sent to 9 | userId: { type: String, required: true, index: true }, 10 | type: { 11 | type: String, 12 | required: true, 13 | enum: [ 14 | 'comment', 15 | 'comment-mention', 16 | 'contribution-mention', 17 | 'following-contribution' 18 | ] 19 | }, 20 | relatedUserId: { type: String, index: true }, 21 | relatedContributionId: { type: String, index: true }, 22 | relatedOrganizationId: { type: String, index: true }, 23 | relatedCommentId: { type: String }, 24 | unseen: { type: Boolean, default: true, index: true }, 25 | createdAt: { type: Date, default: Date.now }, 26 | updatedAt: { type: Date, default: Date.now }, 27 | wasSeeded: { type: Boolean } 28 | }); 29 | 30 | return mongooseClient.model('notifications', notifications); 31 | }; 32 | -------------------------------------------------------------------------------- /server/models/pages.model.js: -------------------------------------------------------------------------------- 1 | // pages-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const pages = new mongooseClient.Schema({ 8 | title: { type: String, required: true }, 9 | slug: { type: String, required: true, index: true }, 10 | type: { type: String, required: true, default: 'page' }, 11 | key: { type: String, required: true, index: true }, 12 | content: { type: String, required: true }, 13 | language: { type: String, required: true, index: true }, 14 | active: { type: Boolean, default: true, index: true }, 15 | createdAt: { type: Date, default: Date.now }, 16 | updatedAt: { type: Date, default: Date.now }, 17 | wasSeeded: { type: Boolean } 18 | }); 19 | 20 | pages.index( 21 | { slug: 1, language: 1 }, 22 | { unique: true } 23 | ); 24 | 25 | return mongooseClient.model('pages', pages); 26 | }; 27 | -------------------------------------------------------------------------------- /server/models/projects.model.js: -------------------------------------------------------------------------------- 1 | // projects-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const { Schema } = mongooseClient; 8 | const projects = new Schema({ 9 | name: { type: String, required: true }, 10 | slug: { type: String }, 11 | followerIds: [], 12 | categoryIds: { type: Array }, 13 | logo: { type: String }, 14 | userId: { type: String, required: true }, 15 | description: { type: String, required: true }, 16 | content: { type: String, required: true }, 17 | addresses: { type: Array, default: [] }, 18 | createdAt: { type: Date, default: Date.now }, 19 | updatedAt: { type: Date, default: Date.now }, 20 | wasSeeded: { type: Boolean } 21 | }); 22 | 23 | return mongooseClient.model('projects', projects); 24 | }; 25 | -------------------------------------------------------------------------------- /server/models/settings.model.js: -------------------------------------------------------------------------------- 1 | // settings-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const { Schema } = mongooseClient; 8 | 9 | const settings = new Schema({ 10 | key: {type: String, default: 'system', index: true, unique: true}, 11 | invites: { 12 | userCanInvite: { type: Boolean, required: true, default: false }, 13 | maxInvitesByUser: { type: Number, required: true, default: 1 }, 14 | onlyUserWithBadgesCanInvite: { type: Array, default: [] } 15 | }, 16 | maintenance: false 17 | }, { 18 | timestamps: true 19 | }); 20 | 21 | return mongooseClient.model('settings', settings); 22 | }; 23 | -------------------------------------------------------------------------------- /server/models/shouts.model.js: -------------------------------------------------------------------------------- 1 | // users-shouts-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const shouts = new mongooseClient.Schema({ 8 | userId: { type: String, required: true, index: true }, 9 | foreignId: { type: String, required: true, index: true }, 10 | foreignService: { type: String, required: true, index: true }, 11 | createdAt: { type: Date, default: Date.now }, 12 | wasSeeded: { type: Boolean } 13 | }); 14 | 15 | shouts.index( 16 | { userId: 1, foreignId: 1 }, 17 | { unique: true } 18 | ); 19 | 20 | return mongooseClient.model('shouts', shouts); 21 | }; 22 | -------------------------------------------------------------------------------- /server/models/status.model.js: -------------------------------------------------------------------------------- 1 | // status-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | 6 | module.exports = function (app) { 7 | const mongooseClient = app.get('mongooseClient'); 8 | const status = new mongooseClient.Schema({ 9 | maintenance: { type: Boolean, default: false }, 10 | updatedAt: { type: Date, default: Date.now } 11 | }); 12 | 13 | return mongooseClient.model('status', status); 14 | }; 15 | -------------------------------------------------------------------------------- /server/models/system-notifications.model.js: -------------------------------------------------------------------------------- 1 | // system-notifications-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const systemNotifications = new mongooseClient.Schema({ 8 | type: { type: String, default: 'info', required: true, index: true }, 9 | title: { type: String, required: true }, 10 | content: { type: String, required: true }, 11 | slot: { type: String, required: true, index: true }, 12 | language: { type: String, required: true, index: true }, 13 | permanent: { type: Boolean, default: false }, 14 | requireConfirmation: { type: Boolean, default: false }, 15 | active: { type: Boolean, default: true, index: true }, 16 | totalCount: { type: Number, default: 0 }, 17 | createdAt: { type: Date, default: Date.now }, 18 | updatedAt: { type: Date, default: Date.now }, 19 | wasSeeded: { type: Boolean } 20 | }); 21 | 22 | return mongooseClient.model('systemNotifications', systemNotifications); 23 | }; 24 | -------------------------------------------------------------------------------- /server/models/users-candos.model.js: -------------------------------------------------------------------------------- 1 | // users-candos-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | module.exports = function (app) { 6 | const mongooseClient = app.get('mongooseClient'); 7 | const usersCandos = new mongooseClient.Schema({ 8 | userId: { type: String, required: true }, 9 | contributionId: { type: String, required: true }, 10 | done: { type: Boolean, default: false }, 11 | doneAt: { type: Date }, 12 | createdAt: { type: Date, default: Date.now }, 13 | updatedAt: { type: Date, default: Date.now }, 14 | wasSeeded: { type: Boolean } 15 | }); 16 | 17 | usersCandos.index( 18 | { userId: 1, contributionId: 1 }, 19 | { unique: true } 20 | ); 21 | 22 | usersCandos.index( 23 | { userId: 1 } 24 | ); 25 | 26 | usersCandos.index( 27 | { contributionId: 1 } 28 | ); 29 | 30 | return mongooseClient.model('usersCandos', usersCandos); 31 | }; 32 | -------------------------------------------------------------------------------- /server/models/usersettings.model.js: -------------------------------------------------------------------------------- 1 | // usersettings-model.js - A mongoose model 2 | // 3 | // See http://mongoosejs.com/docs/models.html 4 | // for more of what you can do here. 5 | 6 | module.exports = function (app) { 7 | const mongooseClient = app.get('mongooseClient'); 8 | const usersettings = new mongooseClient.Schema({ 9 | userId: {type: String, required: true, unique: true}, 10 | blacklist: {type: Array, default: []}, 11 | uiLanguage: {type: String, required: true}, 12 | contentLanguages: {type: Array, default: []}, 13 | filter: { 14 | categoryIds: { type: Array, index: true }, 15 | emotions: { type: Array, index: true } 16 | }, 17 | hideUsersWithoutTermsOfUseSigniture: {type: Boolean}, 18 | updatedAt: { type: Date, default: Date.now } 19 | }); 20 | 21 | return mongooseClient.model('usersettings', usersettings); 22 | }; 23 | -------------------------------------------------------------------------------- /server/mongoose.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const mongoose = require('mongoose'); 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | 9 | mongoose.Promise = global.Promise; 10 | mongoose.connect(app.get('mongodb'), { 11 | useMongoClient: true, 12 | autoReconnect: true, 13 | keepAlive: 1, 14 | connectTimeoutMS: 10000, 15 | socketTimeoutMS: 10000 16 | }).then(() => { 17 | if (process.env.NODE_ENV !== 'production' && app.get('seeder').dropDatabase === true) { 18 | mongoose.connection.dropDatabase().then(() => { 19 | app.debug('>>>>>> DROPED DATABASE <<<<<<'); 20 | let uploadDir = path.resolve('public/uploads'); 21 | if (fs.existsSync(uploadDir)) { 22 | app.debug('Remove the upload directory'); 23 | fs.removeSync(uploadDir); 24 | } 25 | app.emit('mongooseInit'); 26 | }); 27 | } else { 28 | app.emit('mongooseInit'); 29 | } 30 | },(err) => { 31 | console.log(err); 32 | }); 33 | app.set('mongooseClient', mongoose); 34 | }; 35 | -------------------------------------------------------------------------------- /server/seeder/base/index.js: -------------------------------------------------------------------------------- 1 | // These seeders are only used during development 2 | // Use them to seed fake users, contributions, etc. 3 | 4 | module.exports = function () { 5 | // Add your seeder configs here 6 | return [ 7 | require('./categories'), 8 | require('./pages'), 9 | require('./badges') 10 | ]; 11 | }; 12 | -------------------------------------------------------------------------------- /server/seeder/demo/contributions.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | const fs = require('fs'); 3 | const { isEmpty, size } = require('lodash'); 4 | 5 | module.exports = (seederstore) => { 6 | 7 | if (isEmpty(seederstore)) { 8 | throw new Error('Seederstore has to be filled for seeding contributions'); 9 | } 10 | const data = JSON.parse(fs.readFileSync(__dirname + '/data/en_contributions.json', 'utf8')); 11 | 12 | let templates = []; 13 | data.forEach(entry => { 14 | templates.push({ 15 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 16 | title: entry.title, 17 | type: entry.type || 'post', 18 | categoryIds: () => { 19 | if (!isEmpty(entry.categorySlugs)) { 20 | return seedHelpers.mapIdsByKey(seederstore.categories, entry.categorySlugs, 'slug'); 21 | } else { 22 | return seedHelpers.randomCategories(seederstore, false); 23 | } 24 | }, 25 | content: entry.content, 26 | tags: entry.tags || [], 27 | teaserImg: () => entry.teaserImg || seedHelpers.randomUnsplashUrl, 28 | language: entry.language, 29 | shouts: () => seedHelpers.randomItems(seederstore.users, '_id', 0, Math.floor(size(seederstore.users) / 2)), 30 | visibility: 'public', 31 | createdAt: '{{date.recent}}', 32 | updatedAt: '{{date.recent}}', 33 | wasSeeded: true 34 | }); 35 | }); 36 | 37 | return { 38 | services: [{ 39 | randomize: false, 40 | path: 'contributions', 41 | templates: templates 42 | }] 43 | }; 44 | }; -------------------------------------------------------------------------------- /server/seeder/demo/index.js: -------------------------------------------------------------------------------- 1 | // These seeders are only used during development 2 | // Use them to seed fake users, contributions, etc. 3 | 4 | module.exports = function () { 5 | // Add your seeder configs here 6 | return [ 7 | require('./contributions'), 8 | require('../development/emotions') 9 | ]; 10 | }; 11 | -------------------------------------------------------------------------------- /server/seeder/development/comments.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = (seederstore) => { 5 | return { 6 | services: [{ 7 | count: Math.min((_.size(seederstore.contributions) * 3), 100), 8 | path: 'comments', 9 | templates: [ 10 | { 11 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 12 | contributionId: () => seedHelpers.randomItem(seederstore.contributions)._id, 13 | content: '{{lorem.text}} {{lorem.text}}', 14 | language: 'de', 15 | createdAt: '{{date.recent}}', 16 | updatedAt: '{{date.recent}}', 17 | wasSeeded: true 18 | } 19 | ] 20 | }] 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /server/seeder/development/contributions.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | 3 | module.exports = (seederstore) => { 4 | return { 5 | services: [{ 6 | count: 30, 7 | path: 'contributions', 8 | templates: [ 9 | { 10 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 11 | title: '{{lorem.words}}', 12 | type: 'post', 13 | categoryIds: () => seedHelpers.randomCategories(seederstore), 14 | content: '{{lorem.text}} {{lorem.text}}', 15 | teaserImg: seedHelpers.randomUnsplashUrl, 16 | language: () => seedHelpers.randomItem(['de', 'en']), 17 | visibility: 'public', 18 | createdAt: '{{date.recent}}', 19 | updatedAt: '{{date.recent}}', 20 | wasSeeded: true 21 | }, 22 | { 23 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 24 | title: '{{lorem.sentence}}', 25 | type: 'post', 26 | categoryIds: () => seedHelpers.randomCategories(seederstore), 27 | content: '{{lorem.text}} {{lorem.text}}', 28 | language: () => seedHelpers.randomItem(['de', 'en']), 29 | visibility: 'public', 30 | createdAt: '{{date.recent}}', 31 | updatedAt: '{{date.recent}}', 32 | wasSeeded: true 33 | }, 34 | { 35 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 36 | title: '{{lorem.sentence}}', 37 | type: 'post', 38 | categoryIds: () => seedHelpers.randomCategories(seederstore), 39 | content: '{{lorem.text}} {{lorem.text}}', 40 | language: () => seedHelpers.randomItem(['de', 'en']), 41 | visibility: 'public', 42 | isEnabled: false, 43 | createdAt: '{{date.recent}}', 44 | updatedAt: '{{date.recent}}', 45 | wasSeeded: true 46 | } 47 | ] 48 | }] 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /server/seeder/development/emotions.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = (seederstore) => { 5 | return { 6 | services: [ 7 | { 8 | count: Math.round(Math.min((_.size(seederstore.users) * (_.size(seederstore.contributions) * 0.7)), 100)), 9 | path: 'emotions', 10 | template: { 11 | contributionId: () => seedHelpers.randomItem(seederstore.contributions)._id.toString(), 12 | userId: () => seedHelpers.randomItem(seederstore.users)._id.toString(), 13 | rated: () => seedHelpers.randomItem(['funny', 'happy', 'surprised', 'cry', 'angry']), 14 | wasSeeded: true 15 | } 16 | } 17 | ] 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /server/seeder/development/follows.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = (seederstore) => { 5 | return { 6 | services: [ 7 | { 8 | count: _.size(seederstore.users) * 15, 9 | path: 'follows', 10 | templates: [ 11 | { 12 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 13 | foreignId: () => seedHelpers.randomItem(seederstore.users)._id, 14 | foreignService: 'users', 15 | wasSeeded: true 16 | }, 17 | { 18 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 19 | foreignId: () => seedHelpers.randomItem(seederstore.projects)._id, 20 | foreignService: 'projects', 21 | wasSeeded: true 22 | }, 23 | { 24 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 25 | foreignId: () => seedHelpers.randomItem(seederstore.organizations)._id, 26 | foreignService: 'organizations', 27 | wasSeeded: true 28 | } 29 | ] 30 | } 31 | ] 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /server/seeder/development/index.js: -------------------------------------------------------------------------------- 1 | // These seeders are only used during development 2 | // Use them to seed fake users, contributions, etc. 3 | 4 | module.exports = function () { 5 | // Add your seeder configs here 6 | return [ 7 | require('./users-admin'), 8 | require('./users'), 9 | require('./moderators'), 10 | require('./contributions'), 11 | require('./organizations'), 12 | require('./candos'), 13 | require('./users-candos'), 14 | require('./projects'), 15 | require('./follows'), 16 | require('./comments'), 17 | require('./emotions'), 18 | require('./invites'), 19 | require('./shouts'), 20 | require('./usersettings') 21 | ]; 22 | }; 23 | -------------------------------------------------------------------------------- /server/seeder/development/invites.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | module.exports = (seederstore) => { 5 | return { 6 | services: [{ 7 | path: 'invites', 8 | count: 10, 9 | template: { 10 | email: '{{internet.email}}', 11 | code: () => seedHelpers.genInviteCode(), 12 | badgeIds: () => seedHelpers.randomItems(seederstore.badges, '_id', 0, seederstore.badges.length), 13 | language: () => seedHelpers.random(['de', 'en']), 14 | role: () => seedHelpers.random(['admin', 'moderator', 'manager', 'editor', 'user']), 15 | wasSeeded: true 16 | } 17 | }] 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /server/seeder/development/moderators.js: -------------------------------------------------------------------------------- 1 | 2 | // eslint-disable-next-line no-unused-vars 3 | module.exports = (seederstore) => { 4 | return { 5 | services: [{ 6 | path: 'users', 7 | count: 1, 8 | template: { 9 | email: 'test3@test3.de', 10 | password: '1234', 11 | name: 'Mike Moderator', 12 | timezone: 'Europe/Berlin', 13 | avatar: '{{internet.avatar}}', 14 | isVerified : true, 15 | role : 'moderator', 16 | doiToken: null, 17 | confirmedAt: null, 18 | deletedAt: null, 19 | wasSeeded: true 20 | } 21 | }] 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /server/seeder/development/projects.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | 3 | module.exports = (seederstore) => { 4 | return { 5 | services: [{ 6 | path: 'projects', 7 | count: 30, 8 | template: { 9 | name: '{{lorem.slug}}', 10 | followerIds: [], 11 | categoryIds: () => seedHelpers.randomCategories(seederstore), 12 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 13 | addresses: () => seedHelpers.randomAddresses(), 14 | description: '{{lorem.text}}', 15 | content: '{{lorem.text}} {{lorem.text}} {{lorem.text}} {{lorem.text}}', 16 | wasSeeded: true 17 | } 18 | }] 19 | }; 20 | }; -------------------------------------------------------------------------------- /server/seeder/development/shouts.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = (seederstore) => { 5 | return { 6 | services: [{ 7 | count: Math.round(_.size(seederstore.comments) * (_.size(seederstore.users) * 0.5)), 8 | path: 'shouts', 9 | templates: [ 10 | { 11 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 12 | foreignId: () => seedHelpers.randomItem(seederstore.contributions)._id, 13 | foreignService: 'contributions', 14 | wasSeeded: true 15 | } 16 | ] 17 | }] 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /server/seeder/development/users-candos.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | 3 | module.exports = (seederstore) => { 4 | let filter = ({type}) => type === 'cando'; 5 | 6 | return { 7 | services: [{ 8 | count: 50, 9 | path: 'users-candos', 10 | templates: [ 11 | { 12 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 13 | contributionId: () => seedHelpers.randomItem(seederstore.contributions, filter)._id, 14 | done: true, 15 | doneAt: '{{date.recent}}', 16 | createdAt: '{{date.recent}}', 17 | updatedAt: '{{date.recent}}', 18 | wasSeeded: true 19 | }, 20 | { 21 | userId: () => seedHelpers.randomItem(seederstore.users)._id, 22 | contributionId: () => seedHelpers.randomItem(seederstore.contributions, filter)._id, 23 | done: false, 24 | createdAt: '{{date.recent}}', 25 | updatedAt: '{{date.recent}}', 26 | wasSeeded: true 27 | } 28 | ] 29 | }] 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /server/seeder/development/users.js: -------------------------------------------------------------------------------- 1 | const seedHelpers = require('../../helper/seed-helpers'); 2 | const faker = require('faker'); 3 | 4 | // eslint-disable-next-line no-unused-vars 5 | module.exports = (seederstore) => { 6 | return { 7 | services: [{ 8 | path: 'users', 9 | count: 50, 10 | template: { 11 | email: '{{internet.email}}', 12 | password: '{{internet.password}}', 13 | name: '{{name.firstName}} {{name.lastName}}', 14 | slug: '{{lorem.slug}}', 15 | timezone: 'Europe/Berlin', 16 | avatar: () => seedHelpers.randomItem([faker.internet.avatar(), null]), 17 | isVerified : true, 18 | role : 'user', 19 | badgeIds: () => seedHelpers.randomItems(seederstore.badges, '_id', 0, seederstore.badges.length), 20 | doiToken: null, 21 | lastActiveAt: '{{date.recent}}', 22 | confirmedAt: null, 23 | deletedAt: null, 24 | wasSeeded: true 25 | } 26 | }] 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /server/seeder/development/usersettings.js: -------------------------------------------------------------------------------- 1 | const { size, keys } = require('lodash'); 2 | 3 | let userKeys = []; 4 | 5 | // eslint-disable-next-line no-unused-vars 6 | module.exports = (seederstore) => { 7 | userKeys = keys(seederstore.users); 8 | return { 9 | services: [{ 10 | path: 'usersettings', 11 | count: size(seederstore.users), 12 | template: { 13 | userId: () => { 14 | return userKeys.pop(); 15 | }, 16 | uiLanguage: 'de', 17 | contentLanguages: ['de'] 18 | } 19 | }] 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /server/services/admin/admin.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | const { when, isProvider } = require('feathers-hooks-common'); 3 | const isAdmin = require('../../hooks/is-admin'); 4 | 5 | module.exports = { 6 | before: { 7 | all: [ 8 | authenticate('jwt'), 9 | when(isProvider('external'), 10 | isAdmin() 11 | ), 12 | ], 13 | find: [], 14 | get: [], 15 | create: [], 16 | update: [], 17 | patch: [], 18 | remove: [] 19 | }, 20 | 21 | after: { 22 | all: [], 23 | find: [], 24 | get: [], 25 | create: [], 26 | update: [], 27 | patch: [], 28 | remove: [] 29 | }, 30 | 31 | error: { 32 | all: [], 33 | find: [], 34 | get: [], 35 | create: [], 36 | update: [], 37 | patch: [], 38 | remove: [] 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /server/services/admin/admin.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `admin` service on path `/admin` 2 | const createService = require('./admin.class.js'); 3 | const hooks = require('./admin.hooks'); 4 | 5 | module.exports = function () { 6 | const app = this; 7 | const paginate = app.get('paginate'); 8 | 9 | const options = { 10 | app, 11 | name: 'admin', 12 | paginate 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/admin', createService(options)); 17 | 18 | // Get our initialized service so that we can register hooks and filters 19 | const service = app.service('admin'); 20 | 21 | service.hooks(hooks); 22 | }; 23 | -------------------------------------------------------------------------------- /server/services/auth-management/auth-management.hooks.js: -------------------------------------------------------------------------------- 1 | const isEnabled = require('../../hooks/is-enabled'); 2 | const { authenticate } = require('@feathersjs/authentication').hooks; 3 | const { iff, lowerCase } = require('feathers-hooks-common'); 4 | 5 | const isAction = () => { 6 | let args = Array.from(arguments); 7 | return hook => args.includes(hook.data.action); 8 | }; 9 | 10 | module.exports = { 11 | before: { 12 | all: [ 13 | lowerCase('value.email', 'value.username') 14 | ], 15 | find: [], 16 | get: [], 17 | create: [ 18 | iff( 19 | isAction('passwordChange', 'identityChange'), 20 | [ 21 | authenticate('jwt'), 22 | isEnabled() 23 | ] 24 | ), 25 | ], 26 | update: [], 27 | patch: [], 28 | remove: [] 29 | }, 30 | 31 | after: { 32 | all: [], 33 | find: [], 34 | get: [], 35 | create: [], 36 | update: [], 37 | patch: [], 38 | remove: [] 39 | }, 40 | 41 | error: { 42 | all: [], 43 | find: [], 44 | get: [], 45 | create: [], 46 | update: [], 47 | patch: [], 48 | remove: [] 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /server/services/auth-management/auth-management.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `authManagement` service on path `/authManagement` 2 | const authManagement = require('feathers-authentication-management'); 3 | const hooks = require('./auth-management.hooks'); 4 | const notifier = require('./notifier'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | 9 | // Initialize our service with any options it requires 10 | app.configure(authManagement(notifier(app))); 11 | 12 | // Get our initialized service so that we can register hooks and filters 13 | const service = app.service('authManagement'); 14 | 15 | service.hooks(hooks); 16 | }; 17 | -------------------------------------------------------------------------------- /server/services/badges/badges.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | 3 | module.exports = { 4 | before: { 5 | all: [], 6 | find: [], 7 | get: [], 8 | create: [authenticate('jwt')], 9 | update: [authenticate('jwt')], 10 | patch: [authenticate('jwt')], 11 | remove: [authenticate('jwt')] 12 | }, 13 | 14 | after: { 15 | all: [], 16 | find: [], 17 | get: [], 18 | create: [], 19 | update: [], 20 | patch: [], 21 | remove: [] 22 | }, 23 | 24 | error: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /server/services/badges/badges.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `badges` service on path `/badges` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/badges.model'); 4 | const hooks = require('./badges.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | const options = { 11 | name: 'badges', 12 | Model, 13 | paginate 14 | }; 15 | 16 | // Initialize our service with any options it requires 17 | app.use('/badges', createService(options)); 18 | 19 | // Get our initialized service so that we can register hooks and filters 20 | const service = app.service('badges'); 21 | 22 | service.hooks(hooks); 23 | }; 24 | -------------------------------------------------------------------------------- /server/services/categories/categories.hooks.js: -------------------------------------------------------------------------------- 1 | const isAdmin = require('../../hooks/is-admin'); 2 | const { when, isProvider } = require('feathers-hooks-common'); 3 | const createSlug = require('../../hooks/create-slug'); 4 | 5 | module.exports = { 6 | before: { 7 | all: [], 8 | find: [], 9 | get: [], 10 | create: [ 11 | // We don't need admin rights 12 | // for server generated categories 13 | when(isProvider('external'), 14 | isAdmin() 15 | ), 16 | createSlug({ field: 'title' }) 17 | ], 18 | update: [ 19 | isAdmin() 20 | ], 21 | patch: [ 22 | isAdmin() 23 | ], 24 | remove: [ 25 | isAdmin() 26 | ] 27 | }, 28 | 29 | after: { 30 | all: [], 31 | find: [], 32 | get: [], 33 | create: [], 34 | update: [], 35 | patch: [], 36 | remove: [] 37 | }, 38 | 39 | error: { 40 | all: [], 41 | find: [], 42 | get: [], 43 | create: [], 44 | update: [], 45 | patch: [], 46 | remove: [] 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /server/services/categories/categories.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `categories` service on path `/categories` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/categories.model'); 4 | const hooks = require('./categories.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'categories', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/categories', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('categories'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/comments/comments.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `comments` service on path `/comments` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/comments.model'); 4 | const hooks = require('./comments.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'comments', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/comments', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('comments'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/comments/hooks/create-mention-notifications.js: -------------------------------------------------------------------------------- 1 | // Create user mention notifications for contribution 2 | const logger = require('winston'); 3 | const getMentions = require('../../../helper/get-mentions'); 4 | 5 | module.exports = function() { 6 | return function (hook) { 7 | return new Promise(async (resolve, reject) => { 8 | if (hook.result === undefined) { 9 | return reject('Make sure to run this as an after hook.'); 10 | } 11 | 12 | // Check required fields 13 | if(!hook.result.content || !hook.result._id || !hook.result.userId) { 14 | resolve(hook); 15 | return false; 16 | } 17 | 18 | const comment = hook.result; 19 | const creatorId = hook.result.userId; 20 | 21 | let mentions = await getMentions(hook.app, comment.content); 22 | 23 | // Exit if no user mentions were found 24 | if (!mentions) { 25 | return resolve(hook); 26 | } 27 | 28 | const notifications = []; 29 | 30 | hook.data.userMentions = {}; 31 | Object.keys(mentions).forEach(id => { 32 | // Don't notify creator 33 | if (id !== creatorId) { 34 | // Save user mention ids for later comparison 35 | hook.data.userMentions[id] = true; 36 | 37 | notifications.push({ 38 | userId: id, 39 | type: 'comment-mention', 40 | relatedUserId: creatorId, 41 | relatedCommentId: comment._id, 42 | relatedContributionId: comment.contributionId 43 | }); 44 | } 45 | }); 46 | 47 | if (!notifications.length) { 48 | return resolve(hook); 49 | } 50 | 51 | return hook.app.service('notifications').create(notifications) 52 | .then(() => { 53 | resolve(hook); 54 | }) 55 | .catch(error => { 56 | logger.error(error); 57 | resolve(hook); 58 | }); 59 | }); 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /server/services/comments/hooks/create-notifications.js: -------------------------------------------------------------------------------- 1 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 2 | return function (hook) { 3 | return new Promise(resolve => { 4 | const notificationService = hook.app.service('notifications'); 5 | const contributionService = hook.app.service('contributions'); 6 | 7 | // Check required fields 8 | if(!hook.result || !hook.result._id || !hook.result.userId || !hook.result.contributionId) { 9 | resolve(hook); 10 | return false; 11 | } 12 | 13 | const commentId = hook.result._id; 14 | const contributionId = hook.result.contributionId; 15 | const creatorId = hook.result.userId; 16 | 17 | contributionService.get(contributionId, { _populate: 'skip' }) 18 | .then(result => { 19 | const userId = result.userId; 20 | 21 | // Only create notification for users other than creator 22 | // and only if user has not already been sent a mention 23 | // notification by previous hook 24 | if(userId == creatorId || (hook.data.userMentions && hook.data.userMentions[userId])) { 25 | resolve(hook); 26 | return false; 27 | } 28 | 29 | const notification = { 30 | userId: userId, 31 | type: 'comment', 32 | relatedUserId: creatorId, 33 | relatedContributionId: contributionId, 34 | relatedCommentId: commentId, 35 | }; 36 | 37 | notificationService.create(notification) 38 | .then(resolve(hook)) 39 | .catch(() => { 40 | resolve(hook); 41 | }); 42 | }) 43 | .catch(() => { 44 | resolve(hook); 45 | }); 46 | }); 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /server/services/contributions/contributions.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `contributions` service on path `/contributions` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/contributions.model'); 4 | const hooks = require('./contributions.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'contributions', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/contributions', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('contributions'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/contributions/hooks/create-mention-notifications.js: -------------------------------------------------------------------------------- 1 | // Create user mention notifications for contribution 2 | const logger = require('winston'); 3 | const getMentions = require('../../../helper/get-mentions'); 4 | 5 | module.exports = function() { 6 | return function (hook) { 7 | return new Promise(async (resolve, reject) => { 8 | if (hook.result === undefined) { 9 | return reject('Make sure to run this as an after hook.'); 10 | } 11 | 12 | // Check required fields 13 | if(!hook.result.content || !hook.result._id || !hook.result.userId) { 14 | resolve(hook); 15 | return false; 16 | } 17 | 18 | const contribution = hook.result; 19 | const creatorId = hook.result.userId; 20 | 21 | let mentions = await getMentions(hook.app, contribution.content); 22 | 23 | // Exit if no user mentions were found 24 | if (!mentions) { 25 | return resolve(hook); 26 | } 27 | 28 | const notifications = []; 29 | 30 | hook.data.userMentions = {}; 31 | Object.keys(mentions).forEach(id => { 32 | // Don't notify creator 33 | if (id !== creatorId) { 34 | // Save user mention ids for later comparison 35 | hook.data.userMentions[id] = true; 36 | 37 | notifications.push({ 38 | userId: id, 39 | type: 'contribution-mention', 40 | relatedUserId: creatorId, 41 | relatedContributionId: contribution._id 42 | }); 43 | } 44 | }); 45 | 46 | if (!notifications.length) { 47 | return resolve(hook); 48 | } 49 | 50 | return hook.app.service('notifications').create(notifications) 51 | .then(() => { 52 | resolve(hook); 53 | }) 54 | .catch(error => { 55 | logger.error(error); 56 | resolve(hook); 57 | }); 58 | }); 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /server/services/contributions/hooks/get-associated-can-dos.js: -------------------------------------------------------------------------------- 1 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 2 | return function (hook) { 3 | return new Promise(resolve => { 4 | const contributionService = hook.app.service('contributions'); 5 | const limit = 5; 6 | 7 | // Stop, if we have no result at all 8 | if (!hook.result) { 9 | return resolve(hook); 10 | } 11 | 12 | // Stop, if it was a find method and $limit was not set 13 | if (hook.method === 'find' && (!hook.params.query || (hook.params.query.$limit !== 1 && !hook.params.query.slug))) { 14 | return resolve(hook); 15 | } 16 | 17 | // Stop, if we have an empty array or more then one item 18 | let isArray = hook.result.data && Array.isArray(hook.result.data); 19 | if (isArray && (!hook.result.data.length || hook.result.data.length > 1)) { 20 | return resolve(hook); 21 | } 22 | 23 | let currentData = isArray ? hook.result.data[0] : hook.result; 24 | let categoryIds = currentData.categoryIds; 25 | if (!categoryIds || !categoryIds.length) { 26 | return resolve(hook); 27 | } 28 | 29 | return contributionService.find({ 30 | query: { 31 | type: 'cando', 32 | isEnabled: true, 33 | categoryIds: { 34 | $in: categoryIds 35 | }, 36 | $limit: limit, 37 | }, 38 | _populate: 'skip' 39 | }) 40 | .then(({data}) => { 41 | if (isArray) { 42 | hook.result.data[0].associatedCanDos = data; 43 | } else { 44 | hook.result.associatedCanDos = data; 45 | } 46 | return resolve(hook); 47 | }) 48 | .catch(() => { 49 | hook.app.error('issue while fetching associated candos'); 50 | return resolve(hook); 51 | }); 52 | }); 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /server/services/contributions/hooks/notify-followers.js: -------------------------------------------------------------------------------- 1 | // notify followers for contribution 2 | const logger = require('winston'); 3 | 4 | module.exports = function () { 5 | return function (hook) { 6 | return new Promise(async (resolve, reject) => { 7 | if (hook.result === undefined) { 8 | return reject('Make sure to run this as an after hook.'); 9 | } 10 | 11 | // Check required fields 12 | if (!hook.result.content || !hook.result._id || !hook.result.userId) { 13 | resolve(hook); 14 | return false; 15 | } 16 | 17 | const contribution = hook.result; 18 | const creatorType = hook.result.organizationId ? 'organizations' : 'users'; 19 | const creatorId = hook.result.organizationId || hook.result.userId; 20 | 21 | // get all followers 22 | const followers = await hook.app.service('follows').find({ 23 | query: { 24 | $limit: 5000, 25 | foreignService: creatorType, 26 | foreignId: creatorId 27 | } 28 | }); 29 | 30 | const notifications = []; 31 | 32 | // inform all followers 33 | followers.data.forEach(follower => { 34 | notifications.push({ 35 | userId: follower.userId, 36 | type: 'following-contribution', 37 | relatedUserId: creatorId, 38 | relatedOrganizationId: hook.result.organizationId || null, 39 | relatedContributionId: contribution._id 40 | }); 41 | }); 42 | 43 | if (!notifications.length) { 44 | return resolve(hook); 45 | } 46 | 47 | return hook.app.service('notifications').create(notifications) 48 | .then(() => { 49 | resolve(hook); 50 | }) 51 | .catch(error => { 52 | logger.error(error); 53 | resolve(hook); 54 | }); 55 | }); 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /server/services/emails/emails.hooks.js: -------------------------------------------------------------------------------- 1 | const { disallow } = require('feathers-hooks-common'); 2 | 3 | module.exports = { 4 | before: { 5 | all: [ 6 | disallow('external') 7 | ], 8 | find: [], 9 | get: [], 10 | create: [], 11 | update: [], 12 | patch: [], 13 | remove: [] 14 | }, 15 | 16 | after: { 17 | all: [], 18 | find: [], 19 | get: [], 20 | create: [], 21 | update: [], 22 | patch: [], 23 | remove: [] 24 | }, 25 | 26 | error: { 27 | all: [], 28 | find: [], 29 | get: [], 30 | create: [], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /server/services/emails/emails.service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Needs a /config/local.json configuration with 3 | proper SMTP settings in order to work 4 | see /config/local.example.json 5 | 6 | More information on SMTP settings: 7 | https://nodemailer.com/smtp/ 8 | 9 | Could also use a custom mail transporter: 10 | https://github.com/feathersjs/feathers-mailer 11 | */ 12 | 13 | const hooks = require('./emails.hooks'); 14 | const Mailer = require('feathers-mailer'); 15 | 16 | module.exports = function () { 17 | const app = this; 18 | const smtpConfig = app.get('smtpConfig'); 19 | 20 | // Initialize our service with any options it requires 21 | app.use('/emails', Mailer(smtpConfig)); 22 | 23 | // Get our initialized service so that we can register hooks and filters 24 | const service = app.service('emails'); 25 | 26 | service.hooks(hooks); 27 | }; 28 | -------------------------------------------------------------------------------- /server/services/emotions/emotions.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | const emotionRatingHook = require('./hooks/emotion-rating'); 3 | const { isVerified } = require('feathers-authentication-management').hooks; 4 | const hooks = require('feathers-hooks-common'); 5 | 6 | module.exports = { 7 | before: { 8 | all: [authenticate('jwt')], 9 | find: [], 10 | get: [], 11 | create: [ 12 | hooks.when(hooks.isProvider('external'), 13 | isVerified() 14 | )], 15 | update: [hooks.disallow()], 16 | patch: [hooks.disallow()], 17 | remove: [hooks.disallow('external')] 18 | }, 19 | 20 | after: { 21 | all: [ 22 | // populate({ schema: userSchema }), 23 | // populate({ schema: contributionSchema }) 24 | ], 25 | find: [], 26 | get: [], 27 | create: [emotionRatingHook()], 28 | update: [], 29 | patch: [], 30 | remove: [] 31 | }, 32 | 33 | error: { 34 | all: [], 35 | find: [], 36 | get: [], 37 | create: [], 38 | update: [], 39 | patch: [], 40 | remove: [] 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /server/services/emotions/emotions.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `emotions` service on path `/emotions` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/emotions.model'); 4 | const hooks = require('./emotions.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'emotions', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/emotions', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('emotions'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/follows/follows.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `follows` service on path `/follows` 2 | const createService = require('feathers-mongoose'); 3 | // const createService = require('./follows.class.js'); 4 | const createModel = require('../../models/follows.model'); 5 | const hooks = require('./follows.hooks'); 6 | 7 | module.exports = function () { 8 | const app = this; 9 | const Model = createModel(app); 10 | const paginate = app.get('paginate'); 11 | 12 | const options = { 13 | app, 14 | name: 'follows', 15 | Model, 16 | paginate 17 | }; 18 | 19 | // Initialize our service with any options it requires 20 | app.use('/follows', createService(options)); 21 | 22 | // Get our initialized service so that we can register hooks and filters 23 | const service = app.service('follows'); 24 | 25 | service.hooks(hooks); 26 | }; 27 | -------------------------------------------------------------------------------- /server/services/follows/hooks/set-follow-count.js: -------------------------------------------------------------------------------- 1 | const { getByDot } = require('feathers-hooks-common'); 2 | const { isEmpty } = require('lodash'); 3 | 4 | module.exports = () => async hook => { 5 | 6 | const userId = getByDot(hook, 'params.user._id') || getByDot(hook, 'data.userId'); 7 | 8 | const promises = []; 9 | 10 | const inc = hook.method === 'remove' ? -1 : 1; 11 | 12 | let result = Array.isArray(hook.result) ? hook.result[0] : hook.result; 13 | 14 | if (!isEmpty(result)) { 15 | // set count on user service 16 | promises.push(new Promise(async (resolve) => { 17 | try { 18 | const query = {$inc: {}}; 19 | query.$inc[`followingCounts.${result.foreignService}`] = inc; 20 | await hook.app.service('users') 21 | .patch(userId, query); 22 | 23 | resolve(); 24 | } catch (err) { 25 | hook.app.error(err); 26 | resolve(); 27 | } 28 | })); 29 | 30 | // set count on foreign service 31 | promises.push(new Promise(async (resolve) => { 32 | try { 33 | const query = {$inc: {}}; 34 | query.$inc['followersCounts.users'] = inc; 35 | await hook.app.service(result.foreignService) 36 | .patch(result.foreignId, query); 37 | 38 | resolve(); 39 | } catch (err) { 40 | hook.app.error(err); 41 | resolve(); 42 | } 43 | })); 44 | await Promise.all(promises); 45 | } 46 | 47 | return hook; 48 | }; 49 | -------------------------------------------------------------------------------- /server/services/images/hooks/upload-file.js: -------------------------------------------------------------------------------- 1 | // ToDo: make this hook universal 2 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 3 | return function (hook) { 4 | return new Promise(resolve => { 5 | const uploadService = hook.app.service('uploads'); 6 | const uploadsUrl = hook.app.get('baseURL') + '/uploads/'; 7 | const uri = hook.data.uri; 8 | 9 | if(!uri) { 10 | resolve(hook); 11 | } 12 | 13 | uploadService.create({ uri: uri }) 14 | .then(result => { 15 | hook.data.fileName = uploadsUrl + result.id; 16 | resolve(hook); 17 | }) 18 | .catch(error => { 19 | hook.app.error(error); 20 | resolve(hook); 21 | }); 22 | }); 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/images/images.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | const uploadFile = require('./hooks/upload-file'); 3 | const { 4 | associateCurrentUser, 5 | restrictToOwner 6 | } = require('feathers-authentication-hooks'); 7 | 8 | module.exports = { 9 | before: { 10 | all: [], 11 | find: [], 12 | get: [], 13 | create: [ 14 | authenticate('jwt'), 15 | associateCurrentUser(), 16 | uploadFile() 17 | ], 18 | update: [ 19 | authenticate('jwt'), 20 | restrictToOwner() 21 | ], 22 | patch: [ 23 | authenticate('jwt'), 24 | restrictToOwner() 25 | ], 26 | remove: [ 27 | authenticate('jwt'), 28 | restrictToOwner() 29 | ] 30 | }, 31 | 32 | after: { 33 | all: [], 34 | find: [], 35 | get: [], 36 | create: [], 37 | update: [], 38 | patch: [], 39 | remove: [] 40 | }, 41 | 42 | error: { 43 | all: [], 44 | find: [], 45 | get: [], 46 | create: [], 47 | update: [], 48 | patch: [], 49 | remove: [] 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /server/services/images/images.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `images` service on path `/images` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/images.model'); 4 | const hooks = require('./images.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'images', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/images', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('images'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/invites/hooks/send-invite-email.js: -------------------------------------------------------------------------------- 1 | const accountService = require('../../../services/auth-management/notifier'); 2 | 3 | module.exports = () => hook => { 4 | const invite = hook.result; 5 | 6 | if (hook.app.get('defaultEmail') && hook.data && hook.data.sendEmail === true && invite.email) { 7 | accountService(hook.app).notifier('sendInviteEmail', invite); 8 | return hook; 9 | } else { 10 | hook.app.error('issue sending invite email'); 11 | } 12 | 13 | return hook; 14 | }; 15 | -------------------------------------------------------------------------------- /server/services/invites/invites.hooks.js: -------------------------------------------------------------------------------- 1 | const { when, isProvider, lowerCase } = require('feathers-hooks-common'); 2 | const isAdmin = require('../../hooks/is-admin'); 3 | 4 | const sendInviteEmail = require('./hooks/send-invite-email'); 5 | const restrictAPIToAdmin = when(isProvider('external'), 6 | isAdmin() 7 | ); 8 | 9 | module.exports = { 10 | before: { 11 | all: [ 12 | restrictAPIToAdmin 13 | ], 14 | find: [], 15 | get: [], 16 | create: [ 17 | lowerCase('email', 'username') 18 | ], 19 | update: [], 20 | patch: [], 21 | remove: [] 22 | }, 23 | 24 | after: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [ 29 | sendInviteEmail() 30 | ], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | }, 35 | 36 | error: { 37 | all: [], 38 | find: [], 39 | get: [], 40 | create: [], 41 | update: [], 42 | patch: [], 43 | remove: [] 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /server/services/invites/invites.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `invites` service on path `/invites` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/invites.model'); 4 | const hooks = require('./invites.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'invites', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/invites', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('invites'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/notifications/notifications.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `notifications` service on path `/notifications` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/notifications.model'); 4 | const hooks = require('./notifications.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'notifications', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/notifications', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('notifications'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/organizations/hooks/can-edit-organization.js: -------------------------------------------------------------------------------- 1 | const { getByDot } = require('feathers-hooks-common'); 2 | const { errors } = require('@feathersjs/errors'); 3 | // const { isEmpty } = require('lodash'); 4 | 5 | module.exports = (options = {field: 'organizationId'}) => async hook => { 6 | const currentUserId = getByDot(hook, 'params.user._id'); 7 | if (!currentUserId) { 8 | throw new errors.Forbidden('you can\'t create or edit for that organization'); 9 | } 10 | // const userId = getByDot(hook, 'params.user._id') || getByDot(hook, 'data.userId'); 11 | const organizationId = getByDot(hook, `params.${options.field}`) || getByDot(hook, `data.${options.field}`); 12 | 13 | if (!organizationId) { 14 | // ignore items without organization id 15 | return hook; 16 | } 17 | 18 | // get organization with the given id 19 | const organization = await hook.app.service('organizations').get(organizationId); 20 | 21 | // only allow when the user is assigned with the organization 22 | if (!organization || (organization && organization.userId.toString() !== currentUserId.toString())) { 23 | throw new errors.Forbidden('you can\'t create or edit for that organization'); 24 | } 25 | 26 | return hook; 27 | }; 28 | -------------------------------------------------------------------------------- /server/services/organizations/hooks/save-avatar.js: -------------------------------------------------------------------------------- 1 | // ToDo: make this hook universal 2 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 3 | return function (hook) { 4 | return new Promise(resolve => { 5 | const uploadService = hook.app.service('uploads'); 6 | const uploadsUrl = hook.app.get('baseURL') + '/uploads/'; 7 | const uri = hook.data.avatarUri; 8 | 9 | if(!uri) { 10 | return resolve(hook); 11 | } 12 | 13 | uploadService.create({ uri: uri }) 14 | .then(result => { 15 | hook.data.avatar = uploadsUrl + result.id; 16 | resolve(hook); 17 | }) 18 | .catch(error => { 19 | hook.app.error(error); 20 | resolve(hook); 21 | }); 22 | }); 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/organizations/organizations.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `organizations` service on path `/organizations` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/organizations.model'); 4 | const hooks = require('./organizations.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'organizations', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/organizations', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('organizations'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/pages/pages.hooks.js: -------------------------------------------------------------------------------- 1 | const { unless, isProvider } = require('feathers-hooks-common'); 2 | const { isVerified } = require('feathers-authentication-management').hooks; 3 | const { authenticate } = require('@feathersjs/authentication').hooks; 4 | const isAdmin = require('../../hooks/is-admin'); 5 | const createSlug = require('../../hooks/create-slug'); 6 | 7 | const cleanupHTML = () => { 8 | return (hook) => { 9 | hook.data.content = hook.data.content 10 | .replace(/<[a-z]>[\s]*<\/[a-z]>/igm, '') 11 | .replace(/

[\s]*(
)+[\s]*<\/p>/igm, '
') 12 | .replace(/(
){2,}/igm, '
') 13 | .replace(/[\n]{3,}/igm, '\n\n'); 14 | }; 15 | }; 16 | 17 | module.exports = { 18 | before: { 19 | all: [], 20 | find: [], 21 | get: [], 22 | create: [ 23 | authenticate('jwt'), 24 | unless(isProvider('server'), 25 | isVerified(), 26 | isAdmin() 27 | ), 28 | createSlug({ field: 'key', unique: false }), 29 | cleanupHTML() 30 | ], 31 | update: [ 32 | authenticate('jwt'), 33 | unless(isProvider('server'), 34 | isVerified(), 35 | isAdmin() 36 | ), 37 | cleanupHTML() 38 | ], 39 | patch: [ 40 | authenticate('jwt'), 41 | unless(isProvider('server'), 42 | isVerified(), 43 | isAdmin() 44 | ), 45 | cleanupHTML() 46 | ], 47 | remove: [ 48 | authenticate('jwt'), 49 | isVerified(), 50 | isAdmin() 51 | ] 52 | }, 53 | 54 | after: { 55 | all: [], 56 | find: [], 57 | get: [], 58 | create: [], 59 | update: [], 60 | patch: [], 61 | remove: [] 62 | }, 63 | 64 | error: { 65 | all: [], 66 | find: [], 67 | get: [], 68 | create: [], 69 | update: [], 70 | patch: [], 71 | remove: [] 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /server/services/pages/pages.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `pages` service on path `/pages` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/pages.model'); 4 | const hooks = require('./pages.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'pages', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/pages', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('pages'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/projects/projects.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | const createSlug = require('../../hooks/create-slug'); 3 | const saveRemoteImages = require('../../hooks/save-remote-images'); 4 | 5 | module.exports = { 6 | before: { 7 | all: [], 8 | find: [], 9 | get: [], 10 | create: [ 11 | authenticate('jwt'), 12 | createSlug({ field: 'name' }), 13 | saveRemoteImages(['logo']) 14 | ], 15 | update: [ 16 | authenticate('jwt'), 17 | createSlug({ field: 'name' }), 18 | saveRemoteImages(['logo']) 19 | ], 20 | patch: [ 21 | authenticate('jwt'), 22 | createSlug({ field: 'name' }), 23 | saveRemoteImages(['logo']) 24 | ], 25 | remove: [ authenticate('jwt') ] 26 | }, 27 | 28 | after: { 29 | all: [ 30 | // populate({ schema: userSchema }), 31 | // populate({ schema: followerSchema }) 32 | ], 33 | find: [], 34 | get: [], 35 | create: [], 36 | update: [], 37 | patch: [], 38 | remove: [] 39 | }, 40 | 41 | error: { 42 | all: [], 43 | find: [], 44 | get: [], 45 | create: [], 46 | update: [], 47 | patch: [], 48 | remove: [] 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /server/services/projects/projects.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `projects` service on path `/projects` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/projects.model'); 4 | const hooks = require('./projects.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'projects', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/projects', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('projects'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/search/search.class.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | /* eslint-disable no-unused-vars */ 4 | class Service { 5 | constructor (options) { 6 | this.options = options || {}; 7 | this.app = this.options.app; 8 | } 9 | 10 | find (params) { 11 | let singleParams = _.cloneDeep(params); 12 | delete singleParams.query.$ressources; 13 | let search = params.query.$search; 14 | delete singleParams.query.$search; 15 | return new Promise((resolve, reject) => { 16 | let searchPromises = params.query.$ressources.map(ressource => { 17 | return this.ressourceQuery(ressource, singleParams, search); 18 | }); 19 | return Promise.all(searchPromises) 20 | .then(groups => { 21 | let data = groups.map(group => { 22 | return { 23 | name: group.name, 24 | items: group.data 25 | }; 26 | }); 27 | resolve({ data }); 28 | }) 29 | .catch(error => { 30 | reject(error); 31 | }); 32 | }); 33 | } 34 | 35 | /** 36 | * Create a query like 37 | * { name: { $search: 'searchtext' } 38 | * for given ressource 39 | */ 40 | ressourceQuery (ressource, params, search) { 41 | return new Promise((resolve, reject) => { 42 | params.query.name = { $search: search }; 43 | this.app.service(ressource).find(params) 44 | .then(result => { 45 | result.name = ressource; 46 | resolve(result); 47 | }) 48 | .catch(error => { 49 | reject(error); 50 | }); 51 | }); 52 | } 53 | } 54 | 55 | module.exports = function (options) { 56 | return new Service(options); 57 | }; 58 | 59 | module.exports.Service = Service; 60 | -------------------------------------------------------------------------------- /server/services/search/search.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | 3 | module.exports = { 4 | before: { 5 | all: [ authenticate('jwt') ], 6 | find: [], 7 | get: [], 8 | create: [], 9 | update: [], 10 | patch: [], 11 | remove: [] 12 | }, 13 | 14 | after: { 15 | all: [], 16 | find: [], 17 | get: [], 18 | create: [], 19 | update: [], 20 | patch: [], 21 | remove: [] 22 | }, 23 | 24 | error: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /server/services/search/search.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `search` service on path `/search` 2 | const createService = require('./search.class.js'); 3 | const hooks = require('./search.hooks'); 4 | 5 | module.exports = function () { 6 | const app = this; 7 | const paginate = app.get('paginate'); 8 | 9 | const options = { 10 | name: 'search', 11 | paginate, 12 | app 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/search', createService(options)); 17 | 18 | // Get our initialized service so that we can register hooks and filters 19 | const service = app.service('search'); 20 | 21 | service.hooks(hooks); 22 | }; 23 | -------------------------------------------------------------------------------- /server/services/settings/settings.hooks.js: -------------------------------------------------------------------------------- 1 | const isAdmin = require('../../hooks/is-admin'); 2 | const mapCreateToUpsert = require('../../hooks/map-create-to-upsert'); 3 | 4 | module.exports = { 5 | before: { 6 | all: [], 7 | find: [], 8 | get: [], 9 | create: [ 10 | isAdmin(), 11 | mapCreateToUpsert(context => { 12 | const { data } = context; 13 | return { key: data.key || 'system' }; 14 | }) 15 | ], 16 | update: [ 17 | isAdmin() 18 | ], 19 | patch: [ 20 | isAdmin() 21 | ], 22 | remove: [ 23 | isAdmin() 24 | ] 25 | }, 26 | 27 | after: { 28 | all: [], 29 | find: [], 30 | get: [], 31 | create: [], 32 | update: [], 33 | patch: [], 34 | remove: [] 35 | }, 36 | 37 | error: { 38 | all: [], 39 | find: [], 40 | get: [], 41 | create: [], 42 | update: [], 43 | patch: [], 44 | remove: [] 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /server/services/settings/settings.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `settings` service on path `/settings` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/settings.model'); 4 | const hooks = require('./settings.hooks'); 5 | 6 | module.exports = function (app) { 7 | const Model = createModel(app); 8 | 9 | const options = { 10 | Model 11 | }; 12 | 13 | // Initialize our service with any options it requires 14 | app.use('/settings', createService(options)); 15 | 16 | // Get our initialized service so that we can register hooks 17 | const service = app.service('settings'); 18 | 19 | service.hooks(hooks); 20 | }; 21 | -------------------------------------------------------------------------------- /server/services/shouts/hooks/set-shout-count.js: -------------------------------------------------------------------------------- 1 | const { getItems } = require('feathers-hooks-common'); 2 | const { asyncForEach } = require('../../../helper/seed-helpers'); 3 | 4 | module.exports = () => async (hook) => { 5 | let items = getItems(hook); 6 | if (!Array.isArray(items)) { 7 | items = [items]; 8 | } 9 | await asyncForEach(items, async (result) => { 10 | // get count of all shouts on this thing 11 | const shoutCount = await hook.app.service('shouts').find({ 12 | query: { 13 | foreignService: result.foreignService, 14 | foreignId: result.foreignId, 15 | $limit: 0 16 | } 17 | }); 18 | // update the shout count on the foreign service 19 | try { 20 | await hook.app.service(result.foreignService) 21 | .patch(result.foreignId, { 22 | $set: { 23 | shoutCount: shoutCount.total 24 | } 25 | }); 26 | } catch (err) { 27 | hook.app.error(`issue setting shout count on '${result.foreignService}' with id '${result.foreignId}'`); 28 | hook.app.error(err); 29 | } 30 | }); 31 | return hook; 32 | }; 33 | -------------------------------------------------------------------------------- /server/services/shouts/shouts.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | const { unless, isProvider } = require('feathers-hooks-common'); 3 | const isModerator = require('../../hooks/is-moderator-boolean'); 4 | const setShoutCount = require('./hooks/set-shout-count'); 5 | const { 6 | associateCurrentUser, 7 | restrictToOwner 8 | } = require('feathers-authentication-hooks'); 9 | const { isVerified } = require('feathers-authentication-management').hooks; 10 | 11 | module.exports = { 12 | before: { 13 | all: [], 14 | find: [], 15 | get: [], 16 | create: [ 17 | authenticate('jwt'), 18 | unless(isProvider('server'), 19 | isVerified(), 20 | associateCurrentUser() 21 | ) 22 | ], 23 | update: [ 24 | authenticate('jwt'), 25 | unless(isModerator(), 26 | restrictToOwner() 27 | ) 28 | ], 29 | patch: [ 30 | authenticate('jwt'), 31 | unless(isModerator(), 32 | restrictToOwner() 33 | ) 34 | ], 35 | remove: [ 36 | authenticate('jwt'), 37 | unless(isModerator(), 38 | restrictToOwner() 39 | ) 40 | ] 41 | }, 42 | 43 | after: { 44 | all: [], 45 | find: [], 46 | get: [], 47 | create: [ 48 | setShoutCount() 49 | ], 50 | update: [ 51 | setShoutCount() 52 | ], 53 | patch: [ 54 | setShoutCount() 55 | ], 56 | remove: [ 57 | setShoutCount() 58 | ] 59 | }, 60 | 61 | error: { 62 | all: [], 63 | find: [], 64 | get: [], 65 | create: [], 66 | update: [], 67 | patch: [], 68 | remove: [] 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /server/services/shouts/shouts.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `shouts` service on path `/shouts` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/shouts.model'); 4 | const hooks = require('./shouts.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'shouts', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/shouts', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('shouts'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/status/status.class.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | const { keyBy, isEmpty } = require('lodash'); 4 | const mongoose = require('mongoose'); 5 | const errors = require('@feathersjs/errors'); 6 | 7 | class Service { 8 | constructor (options) { 9 | this.options = options || {}; 10 | if (!options.app) { 11 | throw new Error('status services missing option.app'); 12 | } 13 | this.app = options.app; 14 | this.seederstore = {}; 15 | this.status = mongoose.model('status'); 16 | } 17 | 18 | async find (params) { 19 | const res = await this.status.findOne(); 20 | if (!res) { 21 | return { maintenance: false }; 22 | } 23 | 24 | let output = Object.assign({ maintenance: false }, res._doc); 25 | try { 26 | delete output['_id']; 27 | delete output['__v']; 28 | } catch (err) { 29 | return output; 30 | } 31 | return output; 32 | } 33 | 34 | get (id, params) { 35 | throw new errors.NotImplemented(); 36 | } 37 | 38 | create (data, params) { 39 | throw new errors.NotImplemented(); 40 | } 41 | 42 | async update (id, data, params) { 43 | const secret = !isEmpty(params.headers) ? params.headers.secret || null : params.secret || null; 44 | 45 | if (secret !== this.app.get('apiSecret')) { 46 | throw new errors.Forbidden('please provide the correct secret'); 47 | } 48 | 49 | const res = await this.status.update({}, { 50 | $set: Object.assign({ updatedAt: Date.now() }, data) 51 | }, { 52 | upsert: true 53 | }); 54 | return this.find(); 55 | } 56 | 57 | patch (id, data, params) { 58 | throw new errors.NotImplemented(); 59 | } 60 | 61 | remove (id, params) { 62 | throw new errors.NotImplemented(); 63 | } 64 | } 65 | 66 | module.exports = function (options) { 67 | return new Service(options); 68 | }; 69 | 70 | module.exports.Service = Service; 71 | -------------------------------------------------------------------------------- /server/services/status/status.hooks.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | before: { 4 | all: [], 5 | find: [], 6 | get: [], 7 | create: [], 8 | update: [], 9 | patch: [], 10 | remove: [] 11 | }, 12 | 13 | after: { 14 | all: [], 15 | find: [], 16 | get: [], 17 | create: [], 18 | update: [], 19 | patch: [], 20 | remove: [] 21 | }, 22 | 23 | error: { 24 | all: [], 25 | find: [], 26 | get: [], 27 | create: [], 28 | update: [], 29 | patch: [], 30 | remove: [] 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /server/services/status/status.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `status` service on path `/status` 2 | const createService = require('./status.class.js'); 3 | const createModel = require('../../models/status.model'); 4 | const hooks = require('./status.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | app, 13 | name: 'status', 14 | Model, 15 | paginate 16 | }; 17 | 18 | // Initialize our service with any options it requires 19 | app.use('/status', createService(options)); 20 | 21 | // Get our initialized service so that we can register hooks and filters 22 | const service = app.service('status'); 23 | 24 | service.hooks(hooks); 25 | }; 26 | -------------------------------------------------------------------------------- /server/services/system-notifications/system-notifications.hooks.js: -------------------------------------------------------------------------------- 1 | const { unless, isProvider } = require('feathers-hooks-common'); 2 | const { isVerified } = require('feathers-authentication-management').hooks; 3 | const { authenticate } = require('@feathersjs/authentication').hooks; 4 | const isAdmin = require('../../hooks/is-admin'); 5 | 6 | module.exports = { 7 | before: { 8 | all: [], 9 | find: [], 10 | get: [], 11 | create: [ 12 | authenticate('jwt'), 13 | unless(isProvider('server'), 14 | isVerified(), 15 | isAdmin() 16 | ) 17 | ], 18 | update: [ 19 | authenticate('jwt'), 20 | unless(isProvider('server'), 21 | isVerified(), 22 | isAdmin() 23 | ) 24 | ], 25 | patch: [ 26 | authenticate('jwt'), 27 | unless(isProvider('server'), 28 | isVerified(), 29 | isAdmin() 30 | ) 31 | ], 32 | remove: [ 33 | authenticate('jwt'), 34 | isVerified(), 35 | isAdmin() 36 | ] 37 | }, 38 | 39 | after: { 40 | all: [ 41 | ], 42 | find: [ 43 | ], 44 | get: [ 45 | ], 46 | create: [ 47 | ], 48 | update: [ 49 | ], 50 | patch: [], 51 | remove: [] 52 | }, 53 | 54 | error: { 55 | all: [], 56 | find: [], 57 | get: [], 58 | create: [], 59 | update: [], 60 | patch: [], 61 | remove: [] 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /server/services/system-notifications/system-notifications.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `system-notifications` service on path `/system-notifications` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/system-notifications.model'); 4 | const hooks = require('./system-notifications.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'system-notifications', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/system-notifications', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('system-notifications'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/uploads/hooks/encode.js: -------------------------------------------------------------------------------- 1 | const dauria = require('dauria'); 2 | 3 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 4 | return function (hook) { 5 | if (!hook.data.uri && hook.params.file) { 6 | const file = hook.params.file; 7 | const uri = dauria.getBase64DataURI(file.buffer, file.mimetype); 8 | hook.data = { uri: uri }; 9 | 10 | return hook; 11 | } 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /server/services/uploads/uploads.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | const encode = require('./hooks/encode'); 3 | 4 | module.exports = { 5 | before: { 6 | all: [ authenticate('jwt') ], 7 | find: [], 8 | get: [], 9 | create: [ 10 | encode() 11 | ], 12 | update: [], 13 | patch: [], 14 | remove: [] 15 | }, 16 | 17 | after: { 18 | all: [], 19 | find: [], 20 | get: [], 21 | create: [], 22 | update: [], 23 | patch: [], 24 | remove: [] 25 | }, 26 | 27 | error: { 28 | all: [], 29 | find: [], 30 | get: [], 31 | create: [], 32 | update: [], 33 | patch: [], 34 | remove: [] 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /server/services/uploads/uploads.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `uploads` service on path `/uploads` 2 | const hooks = require('./uploads.hooks'); 3 | 4 | // File Service 5 | // feathers-blob service 6 | const blobService = require('feathers-blob'); 7 | // Here we initialize a FileSystem storage, 8 | // but you can use feathers-blob with any other 9 | // storage service like AWS or Google Drive. 10 | const fs = require('fs-blob-store'); 11 | const blobStorage = fs(__dirname + '/../../../public/uploads'); 12 | 13 | // For Multipart Uploads 14 | const multer = require('multer'); 15 | const multipartMiddleware = multer(); 16 | 17 | module.exports = function () { 18 | const app = this; 19 | 20 | const options = { 21 | name: 'uploads', 22 | Model: blobStorage 23 | }; 24 | 25 | // Initialize our service with any options it requires 26 | app.use('/uploads', 27 | // multer parses the file named 'file'. 28 | // Without extra params the data is 29 | // temporary kept in memory 30 | multipartMiddleware.single('file'), 31 | 32 | // another middleware, this time to 33 | // transfer the received file to feathers 34 | function(req,res,next){ 35 | req.feathers.file = req.file; 36 | next(); 37 | }, 38 | blobService(options) 39 | ); 40 | 41 | // Get our initialized service so that we can register hooks and filters 42 | const service = app.service('uploads'); 43 | 44 | service.hooks(hooks); 45 | }; 46 | -------------------------------------------------------------------------------- /server/services/user-invites/user-invites.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | 3 | module.exports = { 4 | before: { 5 | all: [ authenticate('jwt') ], 6 | find: [], 7 | get: [], 8 | create: [], 9 | update: [], 10 | patch: [], 11 | remove: [] 12 | }, 13 | 14 | after: { 15 | all: [ 16 | 17 | ], 18 | find: [], 19 | get: [], 20 | create: [], 21 | update: [], 22 | patch: [], 23 | remove: [] 24 | }, 25 | 26 | error: { 27 | all: [], 28 | find: [], 29 | get: [], 30 | create: [], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /server/services/user-invites/user-invites.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `user-invites` service on path `/user-invites` 2 | const createService = require('./user-invites.class.js'); 3 | const hooks = require('./user-invites.hooks'); 4 | 5 | module.exports = function (app) { 6 | 7 | const paginate = Object.assign(app.get('paginate'), { default: 30 }); 8 | 9 | const options = { 10 | paginate, 11 | app 12 | }; 13 | 14 | // Initialize our service with any options it requires 15 | app.use('/user-invites', createService(options)); 16 | 17 | // Get our initialized service so that we can register hooks 18 | const service = app.service('user-invites'); 19 | 20 | service.hooks(hooks); 21 | }; 22 | -------------------------------------------------------------------------------- /server/services/users-candos/hooks/set-done-date.js: -------------------------------------------------------------------------------- 1 | // Set done date, when cando is done 2 | module.exports = () => hook => { 3 | if (!hook.data.done) { 4 | hook.data.doneAt = null; 5 | } else if (!hook.data.doneAt) { 6 | hook.data.doneAt = Date.now(); 7 | } 8 | return hook; 9 | }; 10 | -------------------------------------------------------------------------------- /server/services/users-candos/users-candos.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | const { unless, isProvider } = require('feathers-hooks-common'); 3 | const isModerator = require('../../hooks/is-moderator-boolean'); 4 | const { 5 | associateCurrentUser, 6 | restrictToOwner 7 | } = require('feathers-authentication-hooks'); 8 | const { isVerified } = require('feathers-authentication-management').hooks; 9 | const setDoneDate = require('./hooks/set-done-date'); 10 | 11 | module.exports = { 12 | before: { 13 | all: [], 14 | find: [], 15 | get: [], 16 | create: [ 17 | unless(isProvider('server'), 18 | authenticate('jwt'), 19 | isVerified(), 20 | associateCurrentUser() 21 | ), 22 | setDoneDate() 23 | ], 24 | update: [ 25 | authenticate('jwt'), 26 | unless(isModerator(), 27 | restrictToOwner() 28 | ), 29 | setDoneDate() 30 | ], 31 | patch: [ 32 | authenticate('jwt'), 33 | unless(isModerator(), 34 | restrictToOwner() 35 | ), 36 | setDoneDate() 37 | ], 38 | remove: [ 39 | authenticate('jwt'), 40 | unless(isModerator(), 41 | restrictToOwner() 42 | ) 43 | ] 44 | }, 45 | 46 | after: { 47 | all: [], 48 | find: [], 49 | get: [], 50 | create: [], 51 | update: [], 52 | patch: [], 53 | remove: [] 54 | }, 55 | 56 | error: { 57 | all: [], 58 | find: [], 59 | get: [], 60 | create: [], 61 | update: [], 62 | patch: [], 63 | remove: [] 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /server/services/users-candos/users-candos.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `users-candos` service on path `/users-candos` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/users-candos.model'); 4 | const hooks = require('./users-candos.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'users-candos', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/users-candos', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('users-candos'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/users/hooks/create-admin.js: -------------------------------------------------------------------------------- 1 | // Create admin user 2 | module.exports = () => hook => { 3 | return new Promise(resolve => { 4 | const userService = hook.app.service('users'); 5 | userService.find() 6 | .then(result => { 7 | // If this is our first user make him almighty 8 | if(result.data.length === 0) { 9 | hook.data.role = 'admin'; 10 | } 11 | resolve(hook); 12 | return; 13 | }) 14 | .catch(error => { 15 | hook.app.error(error); 16 | resolve(hook); 17 | return; 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /server/services/users/hooks/invite-code.js: -------------------------------------------------------------------------------- 1 | const errors = require('@feathersjs/errors'); 2 | 3 | // ToDo: make this hook universal 4 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 5 | return { 6 | before: async function (hook) { 7 | // check the invite code for the given email / code combination 8 | let query = { 9 | query: { 10 | email: hook.data.email, 11 | code: hook.data.inviteCode 12 | } 13 | }; 14 | if (hook.data.invitedByUserId) { 15 | // invite was created 16 | delete query.query.email; 17 | query.query.invitedByUserId = hook.data.invitedByUserId; 18 | } 19 | const inviteRes = await hook.app.service('invites').find(query); 20 | // throw an error if the invite code does not match the one from the invite 21 | if (inviteRes.data.length !== 1) { 22 | throw new errors.Forbidden('invite code is invalid'); 23 | } 24 | 25 | if (inviteRes.data[0].wasUsed) { 26 | throw new errors.Forbidden('invite already used'); 27 | } 28 | 29 | // set some user data from the invite like batches and roles 30 | hook.data.wasInvited = true; 31 | hook.data.inviteId = inviteRes.data[0]._id; 32 | hook.data.role = inviteRes.data[0].role; 33 | hook.data.badgeIds = inviteRes.data[0].badgeIds; 34 | 35 | return hook; 36 | }, 37 | after: async function (hook) { 38 | if (hook.result.wasInvited !== true) { 39 | return hook; 40 | } 41 | 42 | // mark the used inviteCode as used 43 | hook.app.service('invites').patch(hook.data.inviteId, { 44 | wasUsed: true 45 | }).then(res => { 46 | hook.app.debug(res); 47 | }).catch(err => { 48 | hook.app.error(err); 49 | }); 50 | 51 | return hook; 52 | } 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /server/services/users/hooks/is-own-entry.js: -------------------------------------------------------------------------------- 1 | module.exports = (belongs = true) => hook => { 2 | const itemBelongsToAuthenticatedUser = Boolean(hook.params.user && hook.result && hook.params.user._id.toString() === hook.result._id.toString()); 3 | return itemBelongsToAuthenticatedUser === belongs; 4 | }; 5 | -------------------------------------------------------------------------------- /server/services/users/hooks/remove-all-related-user-data.js: -------------------------------------------------------------------------------- 1 | 2 | const errors = require('@feathersjs/errors'); 3 | 4 | module.exports = () => { 5 | return async (hook) => { 6 | async function deleteData (service, query) { 7 | try { 8 | return await hook.app.service(service).remove(null, {query}); 9 | } catch (err) { 10 | hook.app.error('ERROR ON SERVICE' + service); 11 | throw new errors.GeneralError(err.message); 12 | } 13 | } 14 | 15 | if (hook.method !== 'remove') { 16 | hook.app.error('removeAllRelatedUserData hook works only on remove'); 17 | return hook; 18 | } 19 | 20 | const user = hook.params.user; 21 | if (!user || !user._id) { 22 | throw new errors.Forbidden('Forbidden'); 23 | } 24 | 25 | const query = hook.params.query; 26 | 27 | if (query.deleteContributions === true) { 28 | await deleteData('contributions', { 29 | userId: user._id, 30 | type: 'post' 31 | }); 32 | } 33 | delete hook.params.query.deleteContributions; 34 | 35 | if (query.deleteCandos === true) { 36 | await deleteData('contributions', { 37 | userId: user._id, 38 | type: 'cando' 39 | }); 40 | } 41 | delete hook.params.query.deleteCandos; 42 | 43 | if (query.deleteComments === true) { 44 | await deleteData('comments', { 45 | userId: user._id 46 | }); 47 | } 48 | delete hook.params.query.deleteComments; 49 | 50 | await deleteData('shouts', { 51 | userId: user._id 52 | }); 53 | await deleteData('users-candos', { 54 | userId: user._id 55 | }); 56 | await deleteData('notifications', { 57 | userId: user._id 58 | }); 59 | await deleteData('usersettings', { 60 | userId: user._id 61 | }); 62 | await deleteData('invites', { 63 | email: user.email 64 | }); 65 | 66 | return hook; 67 | }; 68 | }; 69 | -------------------------------------------------------------------------------- /server/services/users/hooks/restrict-user-role.js: -------------------------------------------------------------------------------- 1 | // Delete role from data if user is no admin 2 | module.exports = () => hook => { 3 | if(!hook.params || !hook.params.user || hook.params.user.role !== 'admin') { 4 | delete hook.data.role; 5 | } 6 | return hook; 7 | }; 8 | -------------------------------------------------------------------------------- /server/services/users/hooks/save-avatar.js: -------------------------------------------------------------------------------- 1 | // ToDo: make this hook universal 2 | module.exports = function (options = {}) { // eslint-disable-line no-unused-vars 3 | return function (hook) { 4 | return new Promise(resolve => { 5 | const uploadService = hook.app.service('uploads'); 6 | const uploadsUrl = hook.app.get('baseURL') + '/uploads/'; 7 | const uri = hook.data.avatarUri; 8 | 9 | if(!uri) { 10 | return resolve(hook); 11 | } 12 | 13 | uploadService.create({ uri: uri }) 14 | .then(result => { 15 | hook.data.avatar = uploadsUrl + result.id; 16 | resolve(hook); 17 | }) 18 | .catch(error => { 19 | hook.app.error(error); 20 | resolve(hook); 21 | }); 22 | }); 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/users/hooks/send-verification-email.js: -------------------------------------------------------------------------------- 1 | const accountService = require('../../../services/auth-management/notifier'); 2 | 3 | module.exports = () => hook => { 4 | 5 | if (!hook.params.provider) { 6 | hook.app.error('no email provider configured'); 7 | return hook; 8 | } 9 | 10 | const user = hook.result; 11 | 12 | if (hook.app.get('defaultEmail') && hook.data && hook.data.email && user) { 13 | accountService(hook.app).notifier('resendVerifySignup', user); 14 | return hook; 15 | } else { 16 | hook.app.error('issue sending virification email'); 17 | } 18 | 19 | return hook; 20 | }; 21 | -------------------------------------------------------------------------------- /server/services/users/users.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `users` service on path `/users` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/users.model'); 4 | const hooks = require('./users.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'users', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/users', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('users'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/services/usersettings/hooks/validate-blacklist.js: -------------------------------------------------------------------------------- 1 | const { BadRequest } = require('@feathersjs/errors'); 2 | 3 | const validateBlacklist = () => { 4 | return async (hook) => { 5 | const { data } = hook; 6 | if (!data) return hook; 7 | const blacklist = data.blacklist; 8 | if (!blacklist) return hook; 9 | const userId = data.userId; 10 | 11 | if(blacklist && blacklist.includes(userId)) { 12 | throw new BadRequest('You can not blacklist yourself.'); 13 | } 14 | 15 | const users = await hook.app.service('users').find({query: {_id: {$in: blacklist}}}); 16 | const unblacklistable = users.data.find((user) => { 17 | return (user.role === 'admin') || (user.role === 'moderator'); 18 | }); 19 | if (unblacklistable){ 20 | throw new BadRequest('You can not blacklist admin users or moderators.'); 21 | } 22 | return hook; 23 | }; 24 | }; 25 | module.exports = validateBlacklist; 26 | -------------------------------------------------------------------------------- /server/services/usersettings/usersettings.hooks.js: -------------------------------------------------------------------------------- 1 | const { restrictToOwner } = require('feathers-authentication-hooks'); 2 | const mapCreateToUpsert = require('../../hooks/map-create-to-upsert'); 3 | const validateBlacklist = require('./hooks/validate-blacklist'); 4 | const { authenticate } = require('@feathersjs/authentication').hooks; 5 | 6 | module.exports = { 7 | before: { 8 | all: [], 9 | find: [], 10 | get: [], 11 | create: [ 12 | authenticate('jwt'), 13 | validateBlacklist(), 14 | mapCreateToUpsert(context => { 15 | const { data } = context; 16 | return { userId: data.userId }; 17 | }) 18 | ], 19 | update: [ 20 | authenticate('jwt'), 21 | validateBlacklist(), 22 | restrictToOwner() 23 | ], 24 | patch: [ 25 | authenticate('jwt'), 26 | validateBlacklist(), 27 | restrictToOwner() 28 | ], 29 | remove: [ 30 | authenticate('jwt'), 31 | validateBlacklist(), 32 | restrictToOwner() 33 | ] 34 | }, 35 | after: { 36 | all: [], 37 | find: [], 38 | get: [], 39 | create: [], 40 | update: [], 41 | patch: [], 42 | remove: [] 43 | }, 44 | error: { 45 | all: [], 46 | find: [], 47 | get: [], 48 | create: [], 49 | update: [], 50 | patch: [], 51 | remove: [] 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /server/services/usersettings/usersettings.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `users` service on path `/users` 2 | const createService = require('feathers-mongoose'); 3 | const createModel = require('../../models/usersettings.model'); 4 | const hooks = require('./usersettings.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'usersettings', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/usersettings', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('usersettings'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /test/app.test.js.bak: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const rp = require('request-promise'); 3 | const app = require('../server/app'); 4 | 5 | describe('Feathers application tests', () => { 6 | before(function(done) { 7 | this.server = app.listen(3031); 8 | this.server.once('listening', () => done()); 9 | }); 10 | 11 | after(function(done) { 12 | this.server.close(done); 13 | }); 14 | 15 | // it('starts and shows the index page', () => { 16 | // return rp('http://localhost:3031').then(body => 17 | // assert.ok(body.indexOf('') !== -1) 18 | // ); 19 | // }); 20 | 21 | //describe('404', function() { 22 | // it('shows a 404 HTML page', () => { 23 | // return rp({ 24 | // url: 'http://localhost:3031/path/to/nowhere', 25 | // headers: { 26 | // 'Accept': 'text/html' 27 | // } 28 | // }).catch(res => { 29 | // assert.equal(res.statusCode, 404); 30 | // assert.ok(res.error.indexOf('') !== -1); 31 | // }); 32 | // }); 33 | 34 | // it('shows a 404 JSON error without stack trace', () => { 35 | // return rp({ 36 | // url: 'http://localhost:3031/path/to/nowhere', 37 | // json: true 38 | // }).catch(res => { 39 | // assert.equal(res.statusCode, 404); 40 | // assert.equal(res.error.code, 404); 41 | // assert.equal(res.error.message, 'Page not found'); 42 | // assert.equal(res.error.name, 'NotFound'); 43 | // }); 44 | // }); 45 | //}); 46 | }); 47 | -------------------------------------------------------------------------------- /test/assets/categories.js: -------------------------------------------------------------------------------- 1 | const categoryData = { 2 | title: 'a', 3 | icon: 'a' 4 | }; 5 | 6 | module.exports = { 7 | categoryData 8 | }; -------------------------------------------------------------------------------- /test/assets/comments.js: -------------------------------------------------------------------------------- 1 | const commentData = { 2 | content: 'My comment content', 3 | }; 4 | 5 | module.exports = { 6 | commentData 7 | }; -------------------------------------------------------------------------------- /test/assets/contributions.js: -------------------------------------------------------------------------------- 1 | const contributionData = { 2 | title: 'a', 3 | type: 'post', 4 | content: 'My contribution content', 5 | language: 'en' 6 | }; 7 | 8 | const contributionData2 = { 9 | title: 'b', 10 | type: 'post', 11 | content: 'My contribution content', 12 | language: 'en' 13 | }; 14 | 15 | const contributionCandoData = { 16 | title: 'c', 17 | type: 'cando', 18 | content: 'My contribution content', 19 | language: 'en', 20 | cando: { 21 | difficulty: 'easy', 22 | reasonTitle: 'a', 23 | reason: 'My cando reason' 24 | } 25 | }; 26 | 27 | module.exports = { 28 | contributionData, 29 | contributionData2, 30 | contributionCandoData 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /test/assets/users.js: -------------------------------------------------------------------------------- 1 | const adminData = { 2 | email: 'test@test.de', 3 | password: '1234', 4 | name: 'Peter', 5 | timezone: 'Europe/Berlin', 6 | badgeIds: [], 7 | role: 'admin' 8 | }; 9 | 10 | const userData = { 11 | email: 'test2@test2.de', 12 | password: '1234', 13 | name: 'John', 14 | timezone: 'Europe/Berlin', 15 | badgeIds: [], 16 | role: 'user' 17 | }; 18 | 19 | module.exports = { 20 | adminData, 21 | userData 22 | }; 23 | -------------------------------------------------------------------------------- /test/hooks/calculate-emiotion-rating.test.js: -------------------------------------------------------------------------------- 1 | /* const assert = require('assert'); 2 | const emotionRatingHook = require('../../server/services/emotions/hooks/emotion-rating'); 3 | 4 | describe('\'emotion-rating\' hook', () => { 5 | it('runs the hook', () => { 6 | // A mock hook object 7 | const mock = {}; 8 | // Initialize our hook with no options 9 | const hook = emotionRatingHook(); 10 | 11 | // Run the hook function (which returns a promise) 12 | // and compare the resulting hook object 13 | return hook(mock).then(result => { 14 | assert.equal(result, mock, 'Returns the expected hook object'); 15 | }); 16 | }); 17 | }); */ 18 | -------------------------------------------------------------------------------- /test/hooks/create-default-avatar.test.js: -------------------------------------------------------------------------------- 1 | /* const assert = require('assert'); 2 | const createDefaultAvatar = require('../../server/hooks/create-default-avatar'); 3 | 4 | describe('\'create-default-avatar\' hook', () => { 5 | it('runs the hook', () => { 6 | // A mock hook object 7 | const mock = {}; 8 | // Initialize our hook with no options 9 | const hook = createDefaultAvatar(); 10 | 11 | // Run the hook function (which returns a promise) 12 | // and compare the resulting hook object 13 | return hook(mock).then(result => { 14 | assert.equal(result, mock, 'Returns the expected hook object'); 15 | }); 16 | }); 17 | }); */ 18 | -------------------------------------------------------------------------------- /test/hooks/exclude-blacklisted.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const feathers = require('@feathersjs/feathers'); 3 | const excludeBlacklisted = require('../../server/hooks/exclude-blacklisted'); 4 | 5 | let app; 6 | let usersettings = {}; 7 | beforeEach(() => { 8 | // Create a new plain Feathers application 9 | app = feathers(); 10 | 11 | // Register a dummy custom service that just return the 12 | // message data back 13 | app.use('/usersettings', { 14 | async find() { 15 | return { 16 | data: [usersettings] 17 | }; 18 | } 19 | }); 20 | }); 21 | 22 | describe('\'exclude-blacklisted\' hook', () => { 23 | context('given a blacklist', () => { 24 | let mock; 25 | beforeEach(() => { 26 | usersettings = { blacklist: ['4711'] }; 27 | mock = { 28 | type: 'before', 29 | method: 'find', 30 | params: { 31 | user: { _id: 'whatever' } 32 | }, 33 | app 34 | }; 35 | }); 36 | 37 | context('query param `userId` is an object', () => { 38 | it('adds one key', () => { 39 | mock.params.query = { 40 | userId: { $ne: 'user id' } 41 | }; 42 | 43 | const hook = excludeBlacklisted(); 44 | return hook(mock).then(result => { 45 | assert.deepEqual(result.params.query.userId, { 46 | $ne: 'user id', 47 | $nin: ['4711'] 48 | }); 49 | }); 50 | }); 51 | }); 52 | 53 | context('query param `userId` is set to an exact id', () => { 54 | it('has no effect', () => { 55 | mock.params.query = { 56 | userId: 'exact user id' 57 | }; 58 | 59 | const hook = excludeBlacklisted(); 60 | return hook(mock).then(result => { 61 | assert.deepEqual(result.params.query.userId, 'exact user id' ); 62 | }); 63 | }); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/hooks/save-remote-images.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const saveRemoteImages = require('../../server/hooks/save-remote-images'); 3 | 4 | describe('\'saveRemoteImages\' hook', () => { 5 | it('runs the hook', () => { 6 | // A mock hook object 7 | const mock = {}; 8 | // Initialize our hook with no options 9 | const hook = saveRemoteImages(); 10 | 11 | // Run the hook function (which returns a promise) 12 | // and compare the resulting hook object 13 | return hook(mock).then(result => { 14 | assert.equal(result, mock, 'Returns the expected hook object'); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/services/admin.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'admin\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('admin'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/badges.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'badges\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('badges'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/categories.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'categories\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('categories'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/emails.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'emails\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('emails'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/emotions.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'emotions\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('emotions'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/images.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'images\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('images'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/invites.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'invites\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('invites'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/notifications.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'notifications\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('notifications'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/organizations.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'organizations\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('organizations'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/projects.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'projects\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('projects'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/search.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'search\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('search'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/status.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'status\' service', () => { 5 | const service = app.service('status'); 6 | 7 | beforeEach(() => { 8 | return service.update({}, { 9 | maintenance: false 10 | }, { secret: app.get('apiSecret') }); 11 | }); 12 | 13 | it('registered the service', () => { 14 | assert.ok(service, 'Registered the service'); 15 | }); 16 | 17 | it('get maintenance mode', async () => { 18 | const status = await service.find(); 19 | assert.equal(status.maintenance, false); 20 | }); 21 | 22 | it('set maintenance mode to true', async () => { 23 | await service.update({}, { 24 | maintenance: true 25 | }, { secret: app.get('apiSecret') }); 26 | const status1 = await service.find(); 27 | assert.equal(status1.maintenance, true); 28 | 29 | const status2 = await service.update({}, { 30 | maintenance: false 31 | }, { secret: app.get('apiSecret') }); 32 | assert.equal(status2.maintenance, false); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/services/uploads.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'uploads\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('uploads'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/user-invites.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'user-invites\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('user-invites'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/users-candos.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../server/app'); 3 | 4 | describe('\'users-candos\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('users-candos'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | --------------------------------------------------------------------------------