mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-01-17 10:21:31 +00:00
Merge branch 'master' of github.com:Ocelot-Social-Community/Ocelot-Social into brand-reformer-network-first-step
Conflicts: webapp/constants/login.js webapp/constants/registration.js
This commit is contained in:
commit
aaa3def677
2
.github/workflows/docker-push.yml
vendored
2
.github/workflows/docker-push.yml
vendored
@ -81,7 +81,7 @@ jobs:
|
||||
type=sha
|
||||
- name: Build and push Docker images
|
||||
id: push
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1
|
||||
with:
|
||||
context: ${{ matrix.app.context }}
|
||||
target: ${{ matrix.app.target }}
|
||||
|
||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@ -64,7 +64,7 @@ jobs:
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
#- name: Repository Dispatch
|
||||
# uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0
|
||||
# uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0
|
||||
# with:
|
||||
# token: ${{ github.token }}
|
||||
# event-type: trigger-ocelot-build-success
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
# client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
- name: Repository Dispatch stage.ocelot.social
|
||||
uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0
|
||||
uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
- name: Repository Dispatch stage.yunite.me
|
||||
uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0
|
||||
uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
|
||||
72
CHANGELOG.md
72
CHANGELOG.md
@ -4,8 +4,80 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [3.4.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.3.0...3.4.0)
|
||||
|
||||
- fix(webapp): fix badge focus [`#8452`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8452)
|
||||
- feat(backend): branding middlewares [`#8429`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8429)
|
||||
- refactor(webapp): make login, registration, password-reset layout brandable [`#8440`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8440)
|
||||
- fix(backend): fixes for branding [`#8449`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8449)
|
||||
- Replace edit link by pencil button [`#8453`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8453)
|
||||
- fix(webapp): refine little things [`#8382`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8382)
|
||||
- fix(webapp): fix admin badges settings [`#8438`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8438)
|
||||
- build(deps): bump peter-evans/repository-dispatch [`#8443`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8443)
|
||||
- build(deps-dev): bump nodemon from 3.1.9 to 3.1.10 in /backend [`#8447`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8447)
|
||||
- build(deps-dev): bump @types/node from 22.14.1 to 22.15.2 in /backend [`#8446`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8446)
|
||||
- build(deps): bump docker/build-push-action from 6.15.0 to 6.16.0 [`#8444`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8444)
|
||||
- build(deps-dev): bump cypress from 14.3.1 to 14.3.2 in the cypress group [`#8442`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8442)
|
||||
- build(deps-dev): bump eslint-import-resolver-typescript in /backend [`#8445`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8445)
|
||||
- build(deps-dev): bump eslint-config-prettier in /backend [`#8370`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8370)
|
||||
- revokeBadge also removes selection [`#8437`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8437)
|
||||
- feat(webapp): badges UI [`#8426`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8426)
|
||||
- feat(backend): lint - detect unused typescript disables [`#8425`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8425)
|
||||
- fix(docu): remove required but missing `frontend/.env` [`#8431`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8431)
|
||||
- refactor(backend): types for neo4j & neode [`#8409`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8409)
|
||||
- lint everything, disable some setup steps for jest [`#8423`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8423)
|
||||
- lint n/no-sync [`#8405`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8405)
|
||||
- fix(backend): fix notification emails with different name [`#8419`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8419)
|
||||
- refactor(backend): default badges, always return a badge [`#8430`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8430)
|
||||
- refactor(backend): allow to set selected badge-slot to null [`#8421`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8421)
|
||||
- chore(frontend): run npm install [`#8432`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8432)
|
||||
- refactor(webapp): refactor branding diverse v2 [`#8427`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8427)
|
||||
- feat(webapp): badges admin settings [`#8401`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8401)
|
||||
- move graphql types into graphql folder [`#8420`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8420)
|
||||
- fix faker image seed [`#8422`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8422)
|
||||
- build(deps-dev): bump @faker-js/faker from 9.6.0 to 9.7.0 in /webapp [`#8411`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8411)
|
||||
- build(deps-dev): bump @faker-js/faker from 9.6.0 to 9.7.0 [`#8414`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8414)
|
||||
- build(deps): bump sanitize-html from 2.15.0 to 2.16.0 in /backend [`#8418`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8418)
|
||||
- build(deps-dev): bump cypress from 14.3.0 to 14.3.1 in the cypress group [`#8413`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8413)
|
||||
- build(deps): bump actions/setup-node from 4.3.0 to 4.4.0 [`#8412`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8412)
|
||||
- refactor(backend): separate queries [`#8358`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8358)
|
||||
- refactor(backend): lint @typescript-eslint/strict [`#8408`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8408)
|
||||
- refactor(backend): lint @typescript-eslint/recommended-requiring-type-checking [`#8407`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8407)
|
||||
- lint @typescript-eslint/recommended [`#8406`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8406)
|
||||
- build(deps): bump nodemailer from 6.10.0 to 6.10.1 in /backend [`#8417`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8417)
|
||||
- build(deps-dev): bump @eslint-community/eslint-plugin-eslint-comments [`#8415`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8415)
|
||||
- feat(backend): badges [`#8391`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8391)
|
||||
- feat(backend): do not notify blocked or muted users [`#8403`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8403)
|
||||
- feat(backend): only one email is sent although more notifications are triggered [`#8400`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8400)
|
||||
- fix(backend): flaky notifications on mention in group unit test [`#8404`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8404)
|
||||
- refactor(webapp): refactor branding of post ribbons and chat etc. [`#8395`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8395)
|
||||
- downgrade sass to 1.77.6 [`#8399`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8399)
|
||||
- mentiioned users in posts and comments of groups [`#8392`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8392)
|
||||
- feat(backend): no notification mails to users online [`#8397`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8397)
|
||||
- Add .nuxt to gitignore [`#8393`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8393)
|
||||
- fix migrations [`#8390`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8390)
|
||||
- chore(frontend): add '.nvmrc' file to new frontend [`#7112`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7112)
|
||||
- refactor(backend): fix is muted by me query [`#8365`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8365)
|
||||
- fix(backend): block/mute chat [`#8364`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8364)
|
||||
- build(deps): bump graphql-upload from 11.0.0 to 13.0.0 in /backend [`#8375`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8375)
|
||||
- build(deps-dev): bump the typescript group across 1 directory with 2 updates [`#8383`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8383)
|
||||
- Bump graphql from 14.7.0 to 15.10.1 in /webapp [`#8157`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8157)
|
||||
- fix(webapp): better settings ux [`#8347`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8347)
|
||||
- Bump bcryptjs from 2.4.3 to 3.0.2 [`#8218`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8218)
|
||||
- Bump bcryptjs from 2.4.3 to 3.0.2 in /backend [`#8224`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8224)
|
||||
- build(deps-dev): bump cypress from 14.2.1 to 14.3.0 in the cypress group [`#8366`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8366)
|
||||
- build(deps-dev): bump eslint-import-resolver-typescript in /backend [`#8369`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8369)
|
||||
- build(deps-dev): bump dotenv from 16.4.7 to 16.5.0 [`#8367`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8367)
|
||||
- build(deps): bump ioredis from 4.16.1 to 5.6.1 in /backend [`#8371`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8371)
|
||||
- build(deps): bump dotenv from 16.4.7 to 16.5.0 in /backend [`#8372`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8372)
|
||||
- build(deps-dev): bump eslint-config-prettier in /webapp [`#8377`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8377)
|
||||
- build(deps-dev): bump @types/node from 22.14.0 to 22.14.1 in /backend [`#8374`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8374)
|
||||
|
||||
#### [3.3.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.2.1...3.3.0)
|
||||
|
||||
> 12 April 2025
|
||||
|
||||
- v3.3.0 [`#8380`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8380)
|
||||
- fix(webapp): refine group muting locales [`#8378`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8378)
|
||||
- chore(backend): add e-mail setting for our new 'mailserver' to our backend .env.template [`#8359`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8359)
|
||||
- refactor(backend): set up smtp pooling for nodemailer [`#8167`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8167)
|
||||
|
||||
@ -187,10 +187,6 @@ $ cp .env.template .env
|
||||
# in folder backend/
|
||||
$ cp .env.template .env
|
||||
|
||||
# in folder frontend/
|
||||
$ cp .env.template .env
|
||||
```
|
||||
|
||||
For Development:
|
||||
|
||||
```bash
|
||||
|
||||
3
backend/.eslintignore
Normal file
3
backend/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
build/
|
||||
coverage/
|
||||
@ -115,7 +115,7 @@ module.exports = {
|
||||
'n/no-callback-literal': 'error',
|
||||
// 'n/no-deprecated-api': 'error', // part of n/recommended
|
||||
// 'n/no-exports-assign': 'error', // part of n/recommended
|
||||
'n/no-extraneous-import': 'off', // TODO // part of n/recommended
|
||||
'n/no-extraneous-import': 'off', // duplicate of import/no-extraneous-dependencies // part of n/recommended
|
||||
// 'n/no-extraneous-require': 'error', // part of n/recommended
|
||||
'n/no-hide-core-modules': 'error',
|
||||
'n/no-missing-import': 'off', // not compatible with typescript // part of n/recommended
|
||||
@ -127,7 +127,7 @@ module.exports = {
|
||||
// 'n/no-process-exit': 'error', // part of n/recommended
|
||||
'n/no-restricted-import': 'error',
|
||||
'n/no-restricted-require': 'error',
|
||||
// 'n/no-sync': 'error',
|
||||
'n/no-sync': 'error',
|
||||
// 'n/no-unpublished-bin': 'error', // part of n/recommended
|
||||
'n/no-unpublished-import': [
|
||||
'error',
|
||||
|
||||
@ -23,12 +23,14 @@ COPY . .
|
||||
ONBUILD COPY ./branding/constants/ src/config/tmp
|
||||
ONBUILD RUN tools/replace-constants.sh
|
||||
ONBUILD COPY ./branding/email/ src/middleware/helpers/email/
|
||||
ONBUILD COPY ./branding/middlewares/ src/middleware/branding/
|
||||
ONBUILD COPY ./branding/data/ src/db/data
|
||||
ONBUILD COPY ./branding/public/ public/
|
||||
ONBUILD RUN yarn install --production=false --frozen-lockfile --non-interactive
|
||||
ONBUILD RUN yarn run build
|
||||
ONBUILD RUN mkdir /build
|
||||
ONBUILD RUN cp -r ./build /build
|
||||
ONBUILD RUN cp -r ./public /build/build
|
||||
ONBUILD RUN cp -r ./public /build
|
||||
ONBUILD RUN cp -r ./package.json yarn.lock /build
|
||||
ONBUILD RUN cd /build && yarn install --production=true --frozen-lockfile --non-interactive
|
||||
|
||||
|
||||
0
backend/branding/middlewares/.gitkeep
Normal file
0
backend/branding/middlewares/.gitkeep
Normal file
0
backend/branding/public/.gitkeep
Normal file
0
backend/branding/public/.gitkeep
Normal file
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-backend",
|
||||
"version": "3.3.0",
|
||||
"version": "3.4.0",
|
||||
"description": "GraphQL Backend for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
@ -12,18 +12,19 @@
|
||||
"build": "tsc && tsc-alias && ./scripts/build.copy.files.sh",
|
||||
"dev": "nodemon --exec ts-node --require tsconfig-paths/register src/ -e js,ts,gql",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,ts,gql",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts ./src",
|
||||
"lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts .",
|
||||
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts",
|
||||
"db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts",
|
||||
"db:seed": "ts-node --require tsconfig-paths/register src/db/seed.ts",
|
||||
"db:data:admin": "ts-node --require tsconfig-paths/register src/db/admin.ts",
|
||||
"db:data:badges": "ts-node --require tsconfig-paths/register src/db/badges.ts",
|
||||
"db:data:branding": "ts-node --require tsconfig-paths/register src/db/data-production.ts",
|
||||
"db:data:branding": "ts-node --require tsconfig-paths/register src/db/data-branding.ts",
|
||||
"db:data:categories": "ts-node --require tsconfig-paths/register src/db/categories.ts",
|
||||
"db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts",
|
||||
"db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create",
|
||||
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js"
|
||||
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js",
|
||||
"prod:db:data:branding": "node build/src/db/data-branding.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/cli": "~7.27.0",
|
||||
@ -104,14 +105,16 @@
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
|
||||
"@faker-js/faker": "9.7.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "^22.15.2",
|
||||
"@types/uuid": "~9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"apollo-server-testing": "~2.11.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-import-resolver-typescript": "^4.3.2",
|
||||
"eslint-import-resolver-typescript": "^4.3.4",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.11.0",
|
||||
"eslint-plugin-n": "^17.17.0",
|
||||
@ -120,7 +123,7 @@
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
"eslint-plugin-security": "^3.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "~3.1.9",
|
||||
"nodemon": "~3.1.10",
|
||||
"prettier": "^3.5.3",
|
||||
"require-json5": "^1.3.0",
|
||||
"rosie": "^2.1.1",
|
||||
|
||||
12
backend/public/img/badges/default_trophy.svg
Normal file
12
backend/public/img/badges/default_trophy.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="400" height="346.67" version="1.1" viewBox="0 0 400 346.67" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient4" x1="708.76" x2="493.17" y1="280.91" y2="65.326" gradientTransform="translate(-404.06 .215)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#c1c1c1" offset="0"/>
|
||||
<stop stop-color="#fcfcfc" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="m-0.21505 173.98 100.65-173.76h198.71l101.08 173.76-99.785 172.04-201.29 0.43011z" fill="#bebebe"/>
|
||||
<path d="m22.482 173.91 89.236-154.07h176.18l89.617 154.07-88.473 152.54-178.47 0.38135z" fill="url(#linearGradient4)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 773 B |
28
backend/public/img/badges/default_verification.svg
Normal file
28
backend/public/img/badges/default_verification.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="513"
|
||||
height="444"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
id="g3">
|
||||
<path
|
||||
fill="#333"
|
||||
d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"
|
||||
id="path1" />
|
||||
<g
|
||||
fill="#ffffff"
|
||||
id="g2"
|
||||
transform="translate(92)">
|
||||
<path
|
||||
d="m 35.01,367.726 c -0.08,-21.169 -0.205,-53.162 21.257,-71.332 3.817,-3.253 9.93,-7.497 17.321,-9.224 2.575,-0.523 4.956,-0.756 7.262,-0.979 4.438,-0.431 8.27,-0.804 12.054,-2.9 l 4.954,-2.846 c 9.87,-5.655 19.194,-10.996 28.226,-17.377 5.085,-3.632 6.726,-15.73 6.095,-25.428 -0.214,-2.792 -1.893,-5.7 -3.67,-8.777 -1.097,-1.901 -2.232,-3.867 -3.065,-5.916 l -0.073,-0.199 a 56.976,56.976 0 0 1 -0.422,-1.443 c -1.195,-4.205 -1.933,-6.378 -2.386,-7.476 -7.029,-0.944 -11.8,-8.647 -12.888,-21.006 l -0.031,-0.557 c -0.645,-12.785 0.808,-16.13 2.316,-17.716 0.24,-0.254 0.505,-0.475 0.783,-0.666 -1.754,-16.051 3.115,-32.521 13.358,-44.704 9.314,-11.079 21.955,-17.18 35.592,-17.18 3.73,0 7.55,0.458 11.355,1.362 25.63,6.228 41.679,30.27 40.062,59.227 0.53,0.251 1.018,0.61 1.44,1.066 2.752,2.964 2.47,10.97 2.22,14.276 l -0.024,0.41 c -0.335,5.236 -0.684,10.65 -3.052,15.73 -1.739,3.918 -4.405,6.242 -6.76,8.29 -2.396,2.089 -4.288,3.735 -5.294,6.885 -0.7,2.416 -1.645,4.866 -2.559,7.235 -1.752,4.538 -3.407,8.827 -3.54,13.244 -0.427,10.222 1.17,18.391 4.172,21.359 5.097,5.163 13.003,9.391 19.978,13.121 1.6,0.855 3.166,1.692 4.654,2.517 9.28,5.052 16.07,7.915 25.309,8.557 9.118,0.849 18.056,5.193 24.754,11.97 0.736,0.641 1.82,1.744 3.694,3.648 4.416,4.492 4.416,4.492 4.426,5.852 l 0.007,0.758 c 10.783,17.702 11.14,40.656 11.415,58.169 l 0.05,3.28 -3.278,0.028 c -42.05,0.363 -84.058,0.677 -126.058,0.993 -42.12,0.314 -84.232,0.632 -126.367,0.994 L 35.024,371 Z"
|
||||
id="path2" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -1,35 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
/* eslint-disable n/no-process-env */
|
||||
/* eslint-disable n/no-unpublished-require */
|
||||
/* eslint-disable n/no-missing-require */
|
||||
import { config } from 'dotenv'
|
||||
|
||||
import emails from './emails'
|
||||
import metadata from './metadata'
|
||||
|
||||
// Load env file
|
||||
if (require.resolve) {
|
||||
try {
|
||||
config({ path: require.resolve('../../.env') })
|
||||
} catch (error) {
|
||||
// This error is thrown when the .env is not found
|
||||
if (error.code !== 'MODULE_NOT_FOUND') {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
config()
|
||||
|
||||
// Use Cypress env or process.env
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
declare let Cypress: any | undefined
|
||||
const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env // eslint-disable-line no-undef
|
||||
const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env
|
||||
|
||||
const environment = {
|
||||
NODE_ENV: env.NODE_ENV || process.env.NODE_ENV,
|
||||
@ -38,7 +22,9 @@ const environment = {
|
||||
PRODUCTION: env.NODE_ENV === 'production',
|
||||
// used for staging enviroments if 'PRODUCTION=true' and 'PRODUCTION_DB_CLEAN_ALLOW=true'
|
||||
PRODUCTION_DB_CLEAN_ALLOW: env.PRODUCTION_DB_CLEAN_ALLOW === 'true' || false, // default = false
|
||||
DISABLED_MIDDLEWARES: (env.NODE_ENV !== 'production' && env.DISABLED_MIDDLEWARES) || false,
|
||||
DISABLED_MIDDLEWARES: ['test', 'development'].includes(env.NODE_ENV as string)
|
||||
? (env.DISABLED_MIDDLEWARES?.split(',') ?? [])
|
||||
: [],
|
||||
}
|
||||
|
||||
const required = {
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
@ -11,6 +8,7 @@ import { getDriver } from './neo4j'
|
||||
|
||||
const defaultAdmin = {
|
||||
email: 'admin@example.org',
|
||||
// eslint-disable-next-line n/no-sync
|
||||
password: hashSync('1234', 10),
|
||||
name: 'admin',
|
||||
id: uuid(),
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { getNeode } from './neo4j'
|
||||
import { trophies, verification } from './seed/badges'
|
||||
@ -12,6 +9,6 @@ import { trophies, verification } from './seed/badges'
|
||||
await trophies()
|
||||
await verification()
|
||||
} finally {
|
||||
await neode.close()
|
||||
neode.close()
|
||||
}
|
||||
})()
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { categories } from '@constants/categories'
|
||||
|
||||
import { getDriver } from './neo4j'
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
// eslint-disable-next-line n/no-unpublished-require, @typescript-eslint/no-var-requires
|
||||
const tsNode = require('ts-node')
|
||||
// eslint-disable-next-line import/no-unassigned-import, import/no-extraneous-dependencies, n/no-unpublished-require
|
||||
// eslint-disable-next-line import/no-unassigned-import, n/no-unpublished-require
|
||||
require('tsconfig-paths/register')
|
||||
|
||||
module.exports = tsNode.register
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { readdir } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
import { getNeode } from './neo4j'
|
||||
|
||||
const dataFolder = path.join(__dirname, 'data/')
|
||||
const neode = getNeode()
|
||||
|
||||
;(async function () {
|
||||
const files = await readdir(dataFolder)
|
||||
files.forEach(async (file) => {
|
||||
for await (const file of files) {
|
||||
if (file.slice(0, -3).endsWith('-branding')) {
|
||||
const importedModule = await import(path.join(dataFolder, file))
|
||||
if (!importedModule.default) {
|
||||
@ -18,5 +20,8 @@ const dataFolder = path.join(__dirname, 'data/')
|
||||
}
|
||||
await importedModule.default()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// close database connection
|
||||
neode.close()
|
||||
})()
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import { Factory } from 'rosie'
|
||||
@ -39,7 +37,7 @@ export const cleanDatabase = async ({ withMigrations } = { withMigrations: false
|
||||
return transaction.run(clean)
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +93,7 @@ Factory.define('basicUser')
|
||||
return slug || slugify(name, { lower: true })
|
||||
})
|
||||
.attr('encryptedPassword', ['password'], (password) => {
|
||||
// eslint-disable-next-line n/no-sync
|
||||
return hashSync(password, 10)
|
||||
})
|
||||
|
||||
|
||||
@ -1,17 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import { getDriver, getNeode } from '@db/neo4j'
|
||||
|
||||
class Store {
|
||||
async init(errFn) {
|
||||
const neode = getNeode()
|
||||
const session = neode.driver.session()
|
||||
const session = neode.session()
|
||||
const txFreshIndicesConstrains = session.writeTransaction(async (txc) => {
|
||||
// drop all indices and constraints
|
||||
await txc.run('CALL apoc.schema.assert({},{},true)')
|
||||
@ -38,6 +34,9 @@ class Store {
|
||||
// we need to have all constraints and indexes defined here. They can not be properly migrated
|
||||
await txFreshIndicesConstrains
|
||||
|
||||
// You have to wait for the schema to install, else the constraints will not be present.
|
||||
// This is a type error of the library
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await getNeode().schema.install()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Successfully created database indices and constraints!')
|
||||
@ -46,8 +45,8 @@ class Store {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
errFn(error)
|
||||
} finally {
|
||||
session.close()
|
||||
neode.driver.close()
|
||||
await session.close()
|
||||
neode.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +75,7 @@ class Store {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +111,7 @@ class Store {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
@ -27,7 +21,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +42,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
import { throwError, concat } from 'rxjs'
|
||||
@ -25,7 +23,8 @@ export const description = `
|
||||
`
|
||||
export function up(next) {
|
||||
const driver = getDriver()
|
||||
const rxSession = driver.rxSession()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rxSession = driver.rxSession() as any
|
||||
rxSession
|
||||
.beginTransaction()
|
||||
.pipe(
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
import { throwError, concat } from 'rxjs'
|
||||
@ -19,7 +17,8 @@ export const description = `
|
||||
`
|
||||
export function up(next) {
|
||||
const driver = getDriver()
|
||||
const rxSession = driver.rxSession()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rxSession = driver.rxSession() as any
|
||||
rxSession
|
||||
.beginTransaction()
|
||||
.pipe(
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -37,20 +31,20 @@ export async function up(_next) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export function down(next) {
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
try {
|
||||
// Rollback your migration here.
|
||||
next()
|
||||
// next()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
next(err)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
@ -39,7 +35,7 @@ export async function up(next) {
|
||||
throw new Error(error)
|
||||
}
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +62,6 @@ export async function down(next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -40,7 +36,7 @@ export async function up(next) {
|
||||
throw new Error(error)
|
||||
}
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +60,6 @@ export async function down(next) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
@ -55,6 +53,7 @@ export async function up(next) {
|
||||
const { pathname } = new URL(url, 'http://example.org')
|
||||
const fileLocation = path.join(__dirname, `../../../public/${pathname}`)
|
||||
const s3Location = `original${pathname}`
|
||||
// eslint-disable-next-line n/no-sync
|
||||
if (existsSync(fileLocation)) {
|
||||
const mimeType = mime.lookup(fileLocation)
|
||||
const params = {
|
||||
@ -91,7 +90,7 @@ export async function up(next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +112,6 @@ export async function down(next) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
/* eslint-disable no-console */
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
@ -66,7 +61,7 @@ export async function up() {
|
||||
console.log('Created image nodes from all user avatars and post images.')
|
||||
printSummaries(stats)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +99,6 @@ export async function down() {
|
||||
console.log('UNDO: Split images from users and posts.')
|
||||
printSummaries(stats)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
@ -28,7 +25,7 @@ export async function up(next) {
|
||||
`)
|
||||
try {
|
||||
// Implement your migration here.
|
||||
const users = await updateDeletedUserAttributes.records.map((record) => record.get('user'))
|
||||
const users = updateDeletedUserAttributes.records.map((record) => record.get('user'))
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(users)
|
||||
await transaction.commit()
|
||||
@ -41,7 +38,7 @@ export async function up(next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
@ -30,7 +27,7 @@ export async function up(next) {
|
||||
`)
|
||||
try {
|
||||
// Implement your migration here.
|
||||
const posts = await updateDeletedPostsAttributes.records.map((record) => record.get('post'))
|
||||
const posts = updateDeletedPostsAttributes.records.map((record) => record.get('post'))
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(posts)
|
||||
await transaction.commit()
|
||||
@ -43,7 +40,7 @@ export async function up(next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import { existsSync } from 'node:fs'
|
||||
|
||||
@ -33,6 +32,7 @@ export async function up(next) {
|
||||
const urls = records.map((record) => record.get('url'))
|
||||
const danglingUrls = urls.filter((url) => {
|
||||
const fileLocation = `public${url}`
|
||||
// eslint-disable-next-line n/no-sync
|
||||
return !existsSync(fileLocation)
|
||||
})
|
||||
await transaction.run(
|
||||
@ -61,7 +61,7 @@ export async function up(next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -27,7 +25,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +48,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -27,7 +25,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +48,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
@ -43,7 +37,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,6 +62,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
@ -33,7 +27,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +54,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -42,7 +36,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +68,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
@ -50,7 +44,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +69,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = 'Add to all existing posts the Article label'
|
||||
@ -30,7 +24,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +48,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = 'Add postType property Article to all posts'
|
||||
@ -30,7 +24,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +48,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-base-to-string */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -26,11 +22,11 @@ export async function up(_next) {
|
||||
`)
|
||||
for (const event of events.records) {
|
||||
let [id, eventStart, eventEnd] = event
|
||||
let date = new Date(eventStart)
|
||||
let date = new Date(eventStart as string)
|
||||
date.setHours(date.getHours() - 1)
|
||||
eventStart = date.toISOString()
|
||||
if (eventEnd) {
|
||||
date = new Date(eventEnd)
|
||||
date = new Date(eventEnd as string)
|
||||
date.setHours(date.getHours() - 1)
|
||||
eventEnd = date.toISOString()
|
||||
}
|
||||
@ -50,7 +46,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +65,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -37,7 +31,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +56,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
@ -37,7 +31,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +57,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
@ -38,7 +32,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +63,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
@ -30,7 +24,7 @@ export async function up(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +46,6 @@ export async function down(_next) {
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable import/no-named-as-default-member */
|
||||
import neo4j from 'neo4j-driver'
|
||||
import neo4j, { Driver } from 'neo4j-driver'
|
||||
import Neode from 'neode'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import models from '@models/index'
|
||||
|
||||
let driver
|
||||
let driver: Driver
|
||||
const defaultOptions = {
|
||||
uri: CONFIG.NEO4J_URI,
|
||||
username: CONFIG.NEO4J_USERNAME,
|
||||
@ -25,7 +23,7 @@ export function getDriver(options = {}) {
|
||||
return driver
|
||||
}
|
||||
|
||||
let neodeInstance
|
||||
let neodeInstance: Neode
|
||||
export function getNeode(options = {}) {
|
||||
if (!neodeInstance) {
|
||||
const { uri, username, password } = { ...defaultOptions, ...options }
|
||||
|
||||
@ -28,7 +28,6 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
||||
|
||||
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
|
||||
/* eslint-disable no-multi-spaces */
|
||||
;(async function () {
|
||||
let authenticatedUser = null
|
||||
const driver = getDriver()
|
||||
@ -1585,7 +1584,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.log('Seeded Data...')
|
||||
await driver.close()
|
||||
await neode.close()
|
||||
neode.close()
|
||||
process.exit(0)
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
@ -1594,4 +1593,3 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
/* eslint-enable no-multi-spaces */
|
||||
|
||||
@ -4,6 +4,7 @@ type Badge {
|
||||
icon: String!
|
||||
createdAt: String
|
||||
description: String!
|
||||
isDefault: Boolean!
|
||||
|
||||
rewarded: [User]! @relation(name: "REWARDED", direction: "OUT")
|
||||
verifies: [User]! @relation(name: "VERIFIES", direction: "OUT")
|
||||
|
||||
@ -125,10 +125,10 @@ type User {
|
||||
|
||||
categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
badgeVerification: Badge @relation(name: "VERIFIES", direction: "IN")
|
||||
badgeVerification: Badge! @neo4j_ignore
|
||||
badgeTrophies: [Badge]! @relation(name: "REWARDED", direction: "IN")
|
||||
badgeTrophiesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
|
||||
badgeTrophiesSelected: [Badge]! @neo4j_ignore
|
||||
badgeTrophiesSelected: [Badge!]! @neo4j_ignore
|
||||
badgeTrophiesUnused: [Badge]! @neo4j_ignore
|
||||
badgeTrophiesUnusedCount: Int! @neo4j_ignore
|
||||
|
||||
@ -252,6 +252,6 @@ type Mutation {
|
||||
# Get a JWT Token for the given Email and password
|
||||
login(email: String!, password: String!): String!
|
||||
|
||||
setTrophyBadgeSelected(slot: Int!, badgeId: ID!): User
|
||||
setTrophyBadgeSelected(slot: Int!, badgeId: ID): User
|
||||
resetTrophyBadgesSelected: User
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
import { hashSync } from 'bcryptjs'
|
||||
|
||||
export default function (args) {
|
||||
// eslint-disable-next-line n/no-sync
|
||||
args.encryptedPassword = hashSync(args.password, 10)
|
||||
delete args.password
|
||||
return args
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getDriver, getNeode } from '@db/neo4j'
|
||||
import User from '@models/User'
|
||||
|
||||
import decode from './decode'
|
||||
import encode from './encode'
|
||||
@ -16,7 +17,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
@ -86,26 +87,28 @@ describe('decode', () => {
|
||||
})
|
||||
|
||||
it('sets `lastActiveAt`', async () => {
|
||||
let user = await neode.first('User', { id: 'u3' })
|
||||
let user = await neode.first<typeof User>('User', { id: 'u3' }, undefined)
|
||||
await expect(user.toJson()).resolves.not.toHaveProperty('lastActiveAt')
|
||||
await decode(driver, validAuthorizationHeader)
|
||||
user = await neode.first('User', { id: 'u3' })
|
||||
user = await neode.first<typeof User>('User', { id: 'u3' }, undefined)
|
||||
await expect(user.toJson()).resolves.toMatchObject({
|
||||
lastActiveAt: expect.any(String),
|
||||
})
|
||||
})
|
||||
|
||||
it('updates `lastActiveAt` for every authenticated request', async () => {
|
||||
let user = await neode.first('User', { id: 'u3' })
|
||||
let user = await neode.first('User', { id: 'u3' }, undefined)
|
||||
await user.update({
|
||||
updatedAt: new Date().toISOString(),
|
||||
lastActiveAt: '2019-10-03T23:33:08.598Z',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
updatedAt: new Date().toISOString() as any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
lastActiveAt: '2019-10-03T23:33:08.598Z' as any,
|
||||
})
|
||||
await expect(user.toJson()).resolves.toMatchObject({
|
||||
lastActiveAt: '2019-10-03T23:33:08.598Z',
|
||||
})
|
||||
await decode(driver, validAuthorizationHeader)
|
||||
user = await neode.first('User', { id: 'u3' })
|
||||
user = await neode.first<typeof User>('User', { id: 'u3' }, undefined)
|
||||
await expect(user.toJson()).resolves.toMatchObject({
|
||||
// should be a different time by now ;)
|
||||
lastActiveAt: expect.not.stringContaining('2019-10-03T23:33'),
|
||||
|
||||
6
backend/src/middleware/branding/brandingMiddlewares.ts
Normal file
6
backend/src/middleware/branding/brandingMiddlewares.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { MiddlewareOrder } from '@middleware/index'
|
||||
|
||||
export default (): MiddlewareOrder[] => {
|
||||
return []
|
||||
}
|
||||
@ -55,22 +55,15 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
hashtagingUser = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
slug: 'al-capone',
|
||||
},
|
||||
{
|
||||
password: '1234',
|
||||
email: 'test@example.org',
|
||||
},
|
||||
)
|
||||
hashtagingUser = await neode.create('User', {
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
slug: 'al-capone',
|
||||
})
|
||||
await neode.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
// eslint-disable-next-line n/no-sync
|
||||
const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8')
|
||||
|
||||
export const notification = readFile('./notification.html')
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
// eslint-disable-next-line n/no-sync
|
||||
const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8')
|
||||
|
||||
export const notification = readFile('./notification.html')
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
// eslint-disable-next-line n/no-sync
|
||||
const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8')
|
||||
|
||||
export const signup = readFile('./signup.html')
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
import { applyMiddleware } from 'graphql-middleware'
|
||||
import { applyMiddleware, IMiddleware } from 'graphql-middleware'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import brandingMiddlewares from './branding/brandingMiddlewares'
|
||||
import chatMiddleware from './chatMiddleware'
|
||||
import excerpt from './excerptMiddleware'
|
||||
import hashtags from './hashtags/hashtagsMiddleware'
|
||||
@ -26,56 +26,44 @@ import userInteractions from './userInteractions'
|
||||
import validation from './validation/validationMiddleware'
|
||||
import xss from './xssMiddleware'
|
||||
|
||||
export default (schema) => {
|
||||
const middlewares = {
|
||||
sentry,
|
||||
permissions,
|
||||
xss,
|
||||
validation,
|
||||
sluggify,
|
||||
excerpt,
|
||||
login,
|
||||
notifications,
|
||||
hashtags,
|
||||
softDelete,
|
||||
includedFields,
|
||||
orderBy,
|
||||
languages,
|
||||
userInteractions,
|
||||
chatMiddleware,
|
||||
}
|
||||
|
||||
let order = [
|
||||
'sentry',
|
||||
'permissions',
|
||||
'xss',
|
||||
// 'activityPub', disabled temporarily
|
||||
'validation',
|
||||
'userInteractions',
|
||||
'sluggify',
|
||||
'languages',
|
||||
'excerpt',
|
||||
'login',
|
||||
'notifications',
|
||||
'hashtags',
|
||||
'softDelete',
|
||||
'includedFields',
|
||||
'orderBy',
|
||||
'chatMiddleware',
|
||||
]
|
||||
|
||||
// add permisions middleware at the first position (unless we're seeding)
|
||||
if (CONFIG.DISABLED_MIDDLEWARES) {
|
||||
const disabledMiddlewares = CONFIG.DISABLED_MIDDLEWARES.split(',')
|
||||
order = order.filter((key) => {
|
||||
if (disabledMiddlewares.includes(key)) {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.log(`Warning: Disabled "${disabledMiddlewares}" middleware.`)
|
||||
}
|
||||
return !disabledMiddlewares.includes(key)
|
||||
})
|
||||
}
|
||||
|
||||
const appliedMiddlewares = order.map((key) => middlewares[key])
|
||||
return applyMiddleware(schema, ...appliedMiddlewares)
|
||||
export interface MiddlewareOrder {
|
||||
order: number
|
||||
name: string
|
||||
middleware: IMiddleware
|
||||
}
|
||||
|
||||
const ocelotMiddlewares: MiddlewareOrder[] = [
|
||||
{ order: -200, name: 'sentry', middleware: sentry },
|
||||
{ order: -190, name: 'permissions', middleware: permissions },
|
||||
{ order: -180, name: 'xss', middleware: xss },
|
||||
{ order: -170, name: 'validation', middleware: validation },
|
||||
{ order: -160, name: 'userInteractions', middleware: userInteractions },
|
||||
{ order: -150, name: 'sluggify', middleware: sluggify },
|
||||
{ order: -140, name: 'languages', middleware: languages },
|
||||
{ order: -130, name: 'excerpt', middleware: excerpt },
|
||||
{ order: -120, name: 'login', middleware: login },
|
||||
{ order: -110, name: 'notifications', middleware: notifications },
|
||||
{ order: -100, name: 'hashtags', middleware: hashtags },
|
||||
{ order: -90, name: 'softDelete', middleware: softDelete },
|
||||
{ order: -80, name: 'includedFields', middleware: includedFields },
|
||||
{ order: -70, name: 'orderBy', middleware: orderBy },
|
||||
{ order: -60, name: 'chatMiddleware', middleware: chatMiddleware },
|
||||
]
|
||||
|
||||
export default (schema) => {
|
||||
const middlewares = ocelotMiddlewares
|
||||
.concat(brandingMiddlewares())
|
||||
.sort((a, b) => a.order - b.order)
|
||||
|
||||
const filteredMiddlewares = middlewares.filter(
|
||||
(middleware) => !CONFIG.DISABLED_MIDDLEWARES.includes(middleware.name),
|
||||
)
|
||||
|
||||
// Warn if we filtered
|
||||
if (middlewares.length < filteredMiddlewares.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Warning: Disabled "${CONFIG.DISABLED_MIDDLEWARES.join(', ')}" middleware.`)
|
||||
}
|
||||
|
||||
return applyMiddleware(schema, ...filteredMiddlewares.map((middleware) => middleware.middleware))
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
const createPostMutation = gql`
|
||||
|
||||
@ -16,20 +16,21 @@ import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
let server, query, mutate, authenticatedUser, emaillessMember
|
||||
|
||||
let postAuthor, groupMember
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const mentionString =
|
||||
'<a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member">@group-member</a>'
|
||||
const mentionString = `
|
||||
<a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member">@group-member</a>
|
||||
<a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member">@email-less-member</a>`
|
||||
|
||||
const createPostMutation = gql`
|
||||
mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) {
|
||||
@ -119,7 +120,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('emails sent for notifications', () => {
|
||||
@ -148,6 +149,11 @@ describe('emails sent for notifications', () => {
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
emaillessMember = await neode.create('User', {
|
||||
id: 'email-less-member',
|
||||
name: 'Email-less Member',
|
||||
slug: 'email-less-member',
|
||||
})
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
@ -171,6 +177,18 @@ describe('emails sent for notifications', () => {
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
authenticatedUser = await emaillessMember.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'public-group',
|
||||
userId: 'group-member',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -188,7 +206,7 @@ describe('emails sent for notifications', () => {
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: `Hello, ${mentionString}, my trusty follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
groupId: 'public-group',
|
||||
},
|
||||
})
|
||||
@ -213,7 +231,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
@ -225,7 +243,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
@ -237,7 +255,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
@ -260,7 +278,7 @@ describe('emails sent for notifications', () => {
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: `Hello, ${mentionString}, my trusty follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
groupId: 'public-group',
|
||||
},
|
||||
})
|
||||
@ -285,7 +303,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
@ -297,7 +315,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
@ -309,7 +327,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
@ -333,7 +351,7 @@ describe('emails sent for notifications', () => {
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: `Hello, ${mentionString}, my trusty follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
groupId: 'public-group',
|
||||
},
|
||||
})
|
||||
@ -358,7 +376,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
@ -370,7 +388,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
@ -382,7 +400,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
@ -407,7 +425,7 @@ describe('emails sent for notifications', () => {
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: `Hello, ${mentionString}, my trusty follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
groupId: 'public-group',
|
||||
},
|
||||
})
|
||||
@ -432,7 +450,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
@ -444,7 +462,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
@ -456,7 +474,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my trusty follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
@ -481,7 +499,7 @@ describe('emails sent for notifications', () => {
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: `Hello, ${mentionString}, my trusty follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
groupId: 'public-group',
|
||||
},
|
||||
})
|
||||
@ -501,7 +519,7 @@ describe('emails sent for notifications', () => {
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
id: 'comment-2',
|
||||
content: `Hello, ${mentionString}, my beloved follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
postId: 'post',
|
||||
},
|
||||
})
|
||||
@ -529,7 +547,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-2',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my beloved follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
@ -541,7 +559,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-2',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my beloved follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_comment',
|
||||
@ -563,7 +581,7 @@ describe('emails sent for notifications', () => {
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: `Hello, ${mentionString}, my trusty follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
groupId: 'public-group',
|
||||
},
|
||||
})
|
||||
@ -583,7 +601,7 @@ describe('emails sent for notifications', () => {
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
id: 'comment-2',
|
||||
content: `Hello, ${mentionString}, my beloved follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
postId: 'post',
|
||||
},
|
||||
})
|
||||
@ -611,7 +629,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-2',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my beloved follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
@ -623,7 +641,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-2',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my beloved follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_comment',
|
||||
@ -646,7 +664,7 @@ describe('emails sent for notifications', () => {
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: `Hello, ${mentionString}, my trusty follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
groupId: 'public-group',
|
||||
},
|
||||
})
|
||||
@ -666,7 +684,7 @@ describe('emails sent for notifications', () => {
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
id: 'comment-2',
|
||||
content: `Hello, ${mentionString}, my beloved follower.`,
|
||||
content: `Hello, ${mentionString}, my trusty followers.`,
|
||||
postId: 'post',
|
||||
},
|
||||
})
|
||||
@ -694,7 +712,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-2',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my beloved follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
@ -706,7 +724,7 @@ describe('emails sent for notifications', () => {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-2',
|
||||
content:
|
||||
'Hello, <a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>, my beloved follower.',
|
||||
'Hello, <br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a><br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>, my trusty followers.',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_comment',
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
@ -14,14 +14,14 @@ import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, firstFollower, secondFollower
|
||||
let postAuthor, firstFollower, secondFollower, thirdFollower, emaillessFollower
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
@ -94,7 +94,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('following users notifications', () => {
|
||||
@ -107,7 +107,7 @@ describe('following users notifications', () => {
|
||||
slug: 'post-author',
|
||||
},
|
||||
{
|
||||
email: 'test@example.org',
|
||||
email: 'post-author@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
@ -119,7 +119,7 @@ describe('following users notifications', () => {
|
||||
slug: 'first-follower',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
email: 'first-follower@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
@ -131,10 +131,27 @@ describe('following users notifications', () => {
|
||||
slug: 'second-follower',
|
||||
},
|
||||
{
|
||||
email: 'test3@example.org',
|
||||
email: 'second-follower@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
thirdFollower = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'third-follower',
|
||||
name: 'Third Follower',
|
||||
slug: 'third-follower',
|
||||
},
|
||||
{
|
||||
email: 'third-follower@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
emaillessFollower = await neode.create('User', {
|
||||
id: 'email-less-follower',
|
||||
name: 'Email-less Follower',
|
||||
slug: 'email-less-follower',
|
||||
})
|
||||
await secondFollower.update({ emailNotificationsFollowingUsers: false })
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await mutate({
|
||||
@ -146,6 +163,16 @@ describe('following users notifications', () => {
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
authenticatedUser = await thirdFollower.toJson()
|
||||
await mutate({
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
authenticatedUser = await emaillessFollower.toJson()
|
||||
await mutate({
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
@ -221,8 +248,43 @@ describe('following users notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('sends only one email, as second follower has emails disabled', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
it('sends notification to the email-less follower', async () => {
|
||||
authenticatedUser = await emaillessFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends only two emails, as second follower has emails disabled and email-less follower has no email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
html: expect.stringContaining('Hello First Follower'),
|
||||
to: 'first-follower@example.org',
|
||||
}),
|
||||
)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
html: expect.stringContaining('Hello Third Follower'),
|
||||
to: 'third-follower@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -17,22 +17,23 @@ import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, groupMember, pendingMember, noMember
|
||||
let postAuthor, groupMember, pendingMember, noMember, emaillessMember
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const mentionString = `
|
||||
<a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member">@no-meber</a>
|
||||
<a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member">@no-member</a>
|
||||
<a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member">@pending-member</a>
|
||||
<a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member">@group-member</a>.
|
||||
<a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member">@email-less-member</a>.
|
||||
`
|
||||
|
||||
const createPostMutation = gql`
|
||||
@ -115,7 +116,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('mentions in groups', () => {
|
||||
@ -168,6 +169,12 @@ describe('mentions in groups', () => {
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
emaillessMember = await neode.create('User', {
|
||||
id: 'email-less-member',
|
||||
name: 'Email-less Member',
|
||||
slug: 'email-less-member',
|
||||
})
|
||||
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
@ -243,6 +250,28 @@ describe('mentions in groups', () => {
|
||||
userId: 'pending-member',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await emaillessMember.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'public-group',
|
||||
userId: 'group-member',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: 'group-member',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'group-member',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
@ -260,8 +289,26 @@ describe('mentions in groups', () => {
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: 'email-less-member',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'email-less-member',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await markAllAsRead()
|
||||
authenticatedUser = await emaillessMember.toJson()
|
||||
await markAllAsRead()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -327,7 +374,7 @@ describe('mentions in groups', () => {
|
||||
__typename: 'Post',
|
||||
id: 'public-post',
|
||||
content:
|
||||
'Hey <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-meber</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br>! Please read this',
|
||||
'Hey <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-member</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>.<br>! Please read this',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
@ -339,7 +386,7 @@ describe('mentions in groups', () => {
|
||||
__typename: 'Post',
|
||||
id: 'public-post',
|
||||
content:
|
||||
'Hey <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-meber</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br>! Please read this',
|
||||
'Hey <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-member</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>.<br>! Please read this',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
@ -351,7 +398,7 @@ describe('mentions in groups', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('sends 3 emails, one for each user', () => {
|
||||
it('sends only 3 emails, one for each user with an email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
@ -423,7 +470,7 @@ describe('mentions in groups', () => {
|
||||
__typename: 'Post',
|
||||
id: 'closed-post',
|
||||
content:
|
||||
'Hey members <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-meber</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br>! Please read this',
|
||||
'Hey members <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-member</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>.<br>! Please read this',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
@ -435,7 +482,7 @@ describe('mentions in groups', () => {
|
||||
__typename: 'Post',
|
||||
id: 'closed-post',
|
||||
content:
|
||||
'Hey members <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-meber</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br>! Please read this',
|
||||
'Hey members <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-member</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>.<br>! Please read this',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
@ -519,7 +566,7 @@ describe('mentions in groups', () => {
|
||||
__typename: 'Post',
|
||||
id: 'hidden-post',
|
||||
content:
|
||||
'Hey hiders <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-meber</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br>! Please read this',
|
||||
'Hey hiders <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-member</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>.<br>! Please read this',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
@ -531,7 +578,7 @@ describe('mentions in groups', () => {
|
||||
__typename: 'Post',
|
||||
id: 'hidden-post',
|
||||
content:
|
||||
'Hey hiders <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-meber</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br>! Please read this',
|
||||
'Hey hiders <br><a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member" target="_blank">@no-member</a><br><a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member" target="_blank">@pending-member</a><br><a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member" target="_blank">@group-member</a>.<br><a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member" target="_blank">@email-less-member</a>.<br>! Please read this',
|
||||
},
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
|
||||
@ -6,15 +6,20 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { cleanDatabase } from '@db/factories'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, firstCommenter, secondCommenter
|
||||
let postAuthor, firstCommenter, secondCommenter, emaillessObserver
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
@ -97,47 +102,52 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('notifications for users that observe a post', () => {
|
||||
beforeAll(async () => {
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
postAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'post-author',
|
||||
name: 'Post Author',
|
||||
slug: 'post-author',
|
||||
},
|
||||
{
|
||||
email: 'test@example.org',
|
||||
email: 'post-author@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
firstCommenter = await neode.create(
|
||||
'User',
|
||||
firstCommenter = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'first-commenter',
|
||||
name: 'First Commenter',
|
||||
slug: 'first-commenter',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
email: 'first-commenter@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
secondCommenter = await neode.create(
|
||||
'User',
|
||||
secondCommenter = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'second-commenter',
|
||||
name: 'Second Commenter',
|
||||
slug: 'second-commenter',
|
||||
},
|
||||
{
|
||||
email: 'test3@example.org',
|
||||
email: 'second-commenter@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
emaillessObserver = await neode.create('User', {
|
||||
id: 'email-less-observer',
|
||||
name: 'Email-less Observer',
|
||||
slug: 'email-less-observer',
|
||||
})
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
@ -147,6 +157,14 @@ describe('notifications for users that observe a post', () => {
|
||||
content: 'This is the content of the post',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await emaillessObserver.toJson()
|
||||
await mutate({
|
||||
mutation: toggleObservePostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
value: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('first comment on the post', () => {
|
||||
@ -198,8 +216,18 @@ describe('notifications for users that observe a post', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('sends one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'post-author@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('second comment on post', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
authenticatedUser = await secondCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: createCommentMutation,
|
||||
@ -277,10 +305,25 @@ describe('notifications for users that observe a post', () => {
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends two emails', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'post-author@example.org',
|
||||
}),
|
||||
)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'first-commenter@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('first commenter unfollows the post and post author comments post', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: toggleObservePostMutation,
|
||||
@ -376,6 +419,15 @@ describe('notifications for users that observe a post', () => {
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'second-commenter@example.org',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -13,9 +13,9 @@ import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
let isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
@ -62,7 +62,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@ -17,14 +17,14 @@ import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, groupMember, pendingMember
|
||||
let postAuthor, groupMember, pendingMember, emaillessMember
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
@ -118,7 +118,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('notify group members of new posts in group', () => {
|
||||
@ -159,6 +159,12 @@ describe('notify group members of new posts in group', () => {
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
emaillessMember = await neode.create('User', {
|
||||
id: 'email-less-member',
|
||||
name: 'Email-less Member',
|
||||
slug: 'email-less-member',
|
||||
})
|
||||
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
@ -186,6 +192,14 @@ describe('notify group members of new posts in group', () => {
|
||||
userId: 'pending-member',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await emaillessMember.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g-1',
|
||||
userId: 'group-member',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
@ -195,6 +209,14 @@ describe('notify group members of new posts in group', () => {
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g-1',
|
||||
userId: 'email-less-member',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@ -18,9 +18,9 @@ import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation'
|
||||
import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation'
|
||||
import createServer, { pubsub } from '@src/server'
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
const sendMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@middleware/helpers/email/sendMail', () => ({
|
||||
sendMail: (notification) => sendMailMock(notification),
|
||||
}))
|
||||
|
||||
const chatMessageTemplateMock = jest.fn()
|
||||
@ -88,7 +88,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -195,8 +195,8 @@ describe('notifications', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
commentContent = 'Commenters comment.'
|
||||
commentAuthor = await neode.create(
|
||||
'User',
|
||||
commentAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'commentAuthor',
|
||||
name: 'Mrs Comment',
|
||||
@ -345,8 +345,8 @@ describe('notifications', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
postAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'postAuthor',
|
||||
name: 'Mrs Post',
|
||||
@ -658,8 +658,8 @@ describe('notifications', () => {
|
||||
beforeEach(async () => {
|
||||
commentContent =
|
||||
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||
commentAuthor = await neode.create(
|
||||
'User',
|
||||
commentAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'commentAuthor',
|
||||
name: 'Mrs Comment',
|
||||
@ -673,15 +673,15 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only one notification with reason mentioned_in_comment', async () => {
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
postAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'MrPostAuthor',
|
||||
name: 'Mr Author',
|
||||
slug: 'mr-author',
|
||||
},
|
||||
{
|
||||
email: 'post-author@example.org',
|
||||
email: 'post-author2@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
@ -756,8 +756,8 @@ describe('notifications', () => {
|
||||
await postAuthor.relateTo(notifiedUser, 'blocked')
|
||||
commentContent =
|
||||
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||
commentAuthor = await neode.create(
|
||||
'User',
|
||||
commentAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'commentAuthor',
|
||||
name: 'Mrs Comment',
|
||||
@ -807,8 +807,8 @@ describe('notifications', () => {
|
||||
await postAuthor.relateTo(notifiedUser, 'muted')
|
||||
commentContent =
|
||||
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||
commentAuthor = await neode.create(
|
||||
'User',
|
||||
commentAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'commentAuthor',
|
||||
name: 'Mrs Comment',
|
||||
@ -879,8 +879,8 @@ describe('notifications', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
chatSender = await neode.create(
|
||||
'User',
|
||||
chatSender = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'chatSender',
|
||||
name: 'chatSender',
|
||||
@ -931,7 +931,7 @@ describe('notifications', () => {
|
||||
content: 'Some nice message to chatReceiver',
|
||||
senderId: 'chatSender',
|
||||
username: 'chatSender',
|
||||
avatar: null,
|
||||
avatar: expect.any(String),
|
||||
date: expect.any(String),
|
||||
saved: true,
|
||||
distributed: false,
|
||||
@ -967,7 +967,7 @@ describe('notifications', () => {
|
||||
content: 'Some nice message to chatReceiver',
|
||||
senderId: 'chatSender',
|
||||
username: 'chatSender',
|
||||
avatar: null,
|
||||
avatar: expect.any(String),
|
||||
date: expect.any(String),
|
||||
saved: true,
|
||||
distributed: false,
|
||||
@ -1046,7 +1046,7 @@ describe('notifications', () => {
|
||||
content: 'Some nice message to chatReceiver',
|
||||
senderId: 'chatSender',
|
||||
username: 'chatSender',
|
||||
avatar: null,
|
||||
avatar: expect.any(String),
|
||||
date: expect.any(String),
|
||||
saved: true,
|
||||
distributed: false,
|
||||
|
||||
@ -13,64 +13,32 @@ import { isUserOnline } from '@middleware/helpers/isUserOnline'
|
||||
import { validateNotifyUsers } from '@middleware/validation/validationMiddleware'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { getUnreadRoomsCount } from '@schema/resolvers/rooms'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { pubsub, NOTIFICATION_ADDED, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } from '@src/server'
|
||||
|
||||
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
||||
|
||||
const queryNotificationEmails = async (context, notificationUserIds) => {
|
||||
if (!notificationUserIds?.length) return []
|
||||
const userEmailCypher = `
|
||||
MATCH (user: User)
|
||||
// blocked users are filtered out from notifications already
|
||||
WHERE user.id in $notificationUserIds
|
||||
WITH user
|
||||
MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
RETURN emailAddress {.email}
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.readTransaction(async (transaction) => {
|
||||
const emailAddressTransactionResponse = await transaction.run(userEmailCypher, {
|
||||
notificationUserIds,
|
||||
})
|
||||
return emailAddressTransactionResponse.records.map((record) => record.get('emailAddress'))
|
||||
})
|
||||
try {
|
||||
const emailAddresses = await writeTxResultPromise
|
||||
return emailAddresses
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
const publishNotifications = async (
|
||||
context,
|
||||
promises,
|
||||
notificationsPromise,
|
||||
emailNotificationSetting: string,
|
||||
emailsSent: string[] = [],
|
||||
): Promise<string[]> => {
|
||||
let notifications = await Promise.all(promises)
|
||||
notifications = notifications.flat()
|
||||
const notificationsEmailAddresses = await queryNotificationEmails(
|
||||
context,
|
||||
notifications.map((notification) => notification.to.id),
|
||||
)
|
||||
notifications.forEach((notificationAdded, index) => {
|
||||
const notifications = await notificationsPromise
|
||||
notifications.forEach((notificationAdded) => {
|
||||
pubsub.publish(NOTIFICATION_ADDED, { notificationAdded })
|
||||
if (
|
||||
notificationAdded.email && // no primary email was found
|
||||
(notificationAdded.to[emailNotificationSetting] ?? true) &&
|
||||
!isUserOnline(notificationAdded.to) &&
|
||||
!emailsSent.includes(notificationsEmailAddresses[index].email)
|
||||
!emailsSent.includes(notificationAdded.email)
|
||||
) {
|
||||
sendMail(
|
||||
notificationTemplate({
|
||||
email: notificationsEmailAddresses[index].email,
|
||||
email: notificationAdded.email,
|
||||
variables: { notification: notificationAdded },
|
||||
}),
|
||||
)
|
||||
emailsSent.push(notificationsEmailAddresses[index].email)
|
||||
emailsSent.push(notificationAdded.email)
|
||||
}
|
||||
})
|
||||
return emailsSent
|
||||
@ -82,7 +50,7 @@ const handleJoinGroup = async (resolve, root, args, context, resolveInfo) => {
|
||||
if (user) {
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context)],
|
||||
notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context),
|
||||
'emailNotificationsGroupMemberJoined',
|
||||
)
|
||||
}
|
||||
@ -95,7 +63,7 @@ const handleLeaveGroup = async (resolve, root, args, context, resolveInfo) => {
|
||||
if (user) {
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyOwnersOfGroup(groupId, userId, 'user_left_group', context)],
|
||||
notifyOwnersOfGroup(groupId, userId, 'user_left_group', context),
|
||||
'emailNotificationsGroupMemberLeft',
|
||||
)
|
||||
}
|
||||
@ -108,7 +76,7 @@ const handleChangeGroupMemberRole = async (resolve, root, args, context, resolve
|
||||
if (user) {
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context)],
|
||||
notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context),
|
||||
'emailNotificationsGroupMemberRoleChanged',
|
||||
)
|
||||
}
|
||||
@ -121,7 +89,7 @@ const handleRemoveUserFromGroup = async (resolve, root, args, context, resolveIn
|
||||
if (user) {
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context)],
|
||||
notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context),
|
||||
'emailNotificationsGroupMemberRemoved',
|
||||
)
|
||||
}
|
||||
@ -135,20 +103,20 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo
|
||||
if (post) {
|
||||
const sentEmails: string[] = await publishNotifications(
|
||||
context,
|
||||
[notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)],
|
||||
notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context),
|
||||
'emailNotificationsMention',
|
||||
)
|
||||
sentEmails.concat(
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyFollowingUsers(post.id, groupId, context)],
|
||||
notifyFollowingUsers(post.id, groupId, context),
|
||||
'emailNotificationsFollowingUsers',
|
||||
sentEmails,
|
||||
),
|
||||
)
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyGroupMembersOfNewPost(post.id, groupId, context)],
|
||||
notifyGroupMembersOfNewPost(post.id, groupId, context),
|
||||
'emailNotificationsPostInGroup',
|
||||
sentEmails,
|
||||
)
|
||||
@ -164,20 +132,18 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI
|
||||
idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id)
|
||||
const sentEmails: string[] = await publishNotifications(
|
||||
context,
|
||||
[
|
||||
notifyUsersOfMention(
|
||||
'Comment',
|
||||
comment.id,
|
||||
idsOfMentionedUsers,
|
||||
'mentioned_in_comment',
|
||||
context,
|
||||
),
|
||||
],
|
||||
notifyUsersOfMention(
|
||||
'Comment',
|
||||
comment.id,
|
||||
idsOfMentionedUsers,
|
||||
'mentioned_in_comment',
|
||||
context,
|
||||
),
|
||||
'emailNotificationsMention',
|
||||
)
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context)],
|
||||
notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context),
|
||||
'emailNotificationsCommentOnObservedPost',
|
||||
sentEmails,
|
||||
)
|
||||
@ -208,17 +174,20 @@ const notifyFollowingUsers = async (postId, groupId, context) => {
|
||||
const cypher = `
|
||||
MATCH (post:Post { id: $postId })<-[:WROTE]-(author:User { id: $userId })<-[:FOLLOWS]-(user:User)
|
||||
OPTIONAL MATCH (post)-[:IN]->(group:Group { id: $groupId })
|
||||
WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public'
|
||||
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WITH post, author, user, emailAddress, group
|
||||
WHERE group IS NULL OR group.groupType = 'public'
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, author, user,
|
||||
WITH notification, author, user, emailAddress.email as email,
|
||||
post {.*, author: properties(author) } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(user),
|
||||
email: email,
|
||||
relatedUser: properties(author)
|
||||
}
|
||||
`
|
||||
@ -233,8 +202,7 @@ const notifyFollowingUsers = async (postId, groupId, context) => {
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
@ -247,23 +215,25 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
|
||||
const reason = 'post_in_group'
|
||||
const cypher = `
|
||||
MATCH (post:Post { id: $postId })<-[:WROTE]-(author:User { id: $userId })
|
||||
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
MATCH (post)-[:IN]->(group:Group { id: $groupId })<-[membership:MEMBER_OF]-(user:User)
|
||||
WHERE NOT membership.role = 'pending'
|
||||
AND NOT (user)-[:MUTED]->(group)
|
||||
AND NOT (user)-[:MUTED]->(author)
|
||||
AND NOT (user)-[:BLOCKED]-(author)
|
||||
AND NOT user.id = $userId
|
||||
WITH post, author, user
|
||||
WITH post, author, user, emailAddress
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, author, user,
|
||||
WITH notification, author, user, emailAddress.email as email,
|
||||
post {.*, author: properties(author) } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(user),
|
||||
email: email,
|
||||
relatedUser: properties(author)
|
||||
}
|
||||
`
|
||||
@ -278,8 +248,7 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
@ -295,12 +264,13 @@ const notifyOwnersOfGroup = async (groupId, userId, reason, context) => {
|
||||
WITH owner, group, user, membership
|
||||
MERGE (group)-[notification:NOTIFIED {reason: $reason}]->(owner)
|
||||
WITH group, owner, notification, user, membership
|
||||
OPTIONAL MATCH (owner)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
SET notification.relatedUserId = $userId
|
||||
WITH owner, group { __typename: 'Group', .*, myRole: membership.roleInGroup } AS finalGroup, user, notification
|
||||
RETURN notification {.*, from: finalGroup, to: properties(owner), relatedUser: properties(user) }
|
||||
WITH owner, emailAddress.email as email, group { __typename: 'Group', .*, myRole: membership.roleInGroup } AS finalGroup, user, notification
|
||||
RETURN notification {.*, from: finalGroup, to: properties(owner), email: email, relatedUser: properties(user) }
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
@ -312,8 +282,7 @@ const notifyOwnersOfGroup = async (groupId, userId, reason, context) => {
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
@ -327,17 +296,18 @@ const notifyMemberOfGroup = async (groupId, userId, reason, context) => {
|
||||
MATCH (owner:User { id: $ownerId })
|
||||
MATCH (user:User { id: $userId })
|
||||
MATCH (group:Group { id: $groupId })
|
||||
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
OPTIONAL MATCH (user)-[membership:MEMBER_OF]->(group)
|
||||
WITH user, group, owner, membership
|
||||
WITH user, group, owner, membership, emailAddress
|
||||
MERGE (group)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
WITH group, user, notification, owner, membership
|
||||
WITH group, user, notification, owner, membership, emailAddress
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
SET notification.relatedUserId = $ownerId
|
||||
WITH group { __typename: 'Group', .*, myRole: membership.roleInGroup } AS finalGroup,
|
||||
notification, user, owner
|
||||
RETURN notification {.*, from: finalGroup, to: properties(user), relatedUser: properties(owner) }
|
||||
notification, user, emailAddress.email as email, owner
|
||||
RETURN notification {.*, from: finalGroup, to: properties(user), email: email, relatedUser: properties(owner) }
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
@ -350,8 +320,7 @@ const notifyMemberOfGroup = async (groupId, userId, reason, context) => {
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
@ -371,11 +340,13 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)-[:BLOCKED]-(author)
|
||||
AND NOT (user)-[:MUTED]->(author)
|
||||
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
OPTIONAL MATCH (post)-[:IN]->(group:Group)
|
||||
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
|
||||
WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
||||
WITH post, author, user, group, emailAddress
|
||||
WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
WITH post AS resource, notification, user
|
||||
WITH post AS resource, notification, user, emailAddress
|
||||
`
|
||||
break
|
||||
}
|
||||
@ -388,25 +359,27 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
AND NOT (user)-[:BLOCKED]-(postAuthor)
|
||||
AND NOT (user)-[:MUTED]->(commenter)
|
||||
AND NOT (user)-[:MUTED]->(postAuthor)
|
||||
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
OPTIONAL MATCH (post)-[:IN]->(group:Group)
|
||||
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
|
||||
WITH comment, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
||||
WITH comment, user, group, emailAddress
|
||||
WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
WITH comment AS resource, notification, user
|
||||
WITH comment AS resource, notification, user, emailAddress
|
||||
`
|
||||
break
|
||||
}
|
||||
}
|
||||
mentionedCypher += `
|
||||
WITH notification, user, resource,
|
||||
WITH notification, user, resource, emailAddress,
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
|
||||
WITH resource, user, notification, authors, posts,
|
||||
WITH resource, user, emailAddress.email as email, notification, authors, posts,
|
||||
resource {.*, __typename: [l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group']][0], author: authors[0], post: posts[0]} AS finalResource
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
RETURN notification {.*, from: finalResource, to: properties(user), relatedUser: properties(user) }
|
||||
RETURN notification {.*, from: finalResource, to: properties(user), email: email, relatedUser: properties(user) }
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
@ -418,8 +391,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
@ -437,18 +409,20 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
||||
WHERE NOT (observingUser)-[:BLOCKED]-(commenter)
|
||||
AND NOT (observingUser)-[:MUTED]->(commenter)
|
||||
AND NOT observingUser.id = $userId
|
||||
WITH observingUser, post, comment, commenter
|
||||
OPTIONAL MATCH (observingUser)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WITH observingUser, emailAddress, post, comment, commenter
|
||||
MATCH (postAuthor:User)-[:WROTE]->(post)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, observingUser, post, commenter, postAuthor,
|
||||
WITH notification, observingUser, emailAddress.email as email, post, commenter, postAuthor,
|
||||
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(observingUser),
|
||||
email: email,
|
||||
relatedUser: properties(commenter)
|
||||
}
|
||||
`,
|
||||
@ -461,8 +435,7 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
return await writeTxResultPromise
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
@ -28,7 +26,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -33,7 +33,7 @@ describe('authorization', () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
|
||||
@ -8,6 +8,7 @@ import { rule, shield, deny, allow, or, and } from 'graphql-shield'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { getNeode } from '@db/neo4j'
|
||||
import SocialMedia from '@models/SocialMedia'
|
||||
import { validateInviteCode } from '@schema/resolvers/transactions/inviteCodes'
|
||||
|
||||
const debug = !!CONFIG.DEBUG
|
||||
@ -48,15 +49,16 @@ const isMySocialMedia = rule({
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
let socialMedia = await neode.find('SocialMedia', args.id)
|
||||
const socialMedia = await neode.find<typeof SocialMedia>('SocialMedia', args.id)
|
||||
// Did we find a social media node?
|
||||
if (!socialMedia) {
|
||||
return false
|
||||
}
|
||||
socialMedia = await socialMedia.toJson() // whats this for?
|
||||
const socialMediaJson = await socialMedia.toJson() // whats this for?
|
||||
|
||||
// Is it my social media entry?
|
||||
return socialMedia.ownedBy.node.id === user.id
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (socialMediaJson.ownedBy as any).node.id === user.id
|
||||
})
|
||||
|
||||
const isAllowedToChangeGroupSettings = rule({
|
||||
|
||||
@ -42,7 +42,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -202,7 +202,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('softDeleteMiddleware', () => {
|
||||
|
||||
@ -46,7 +46,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('middleware/userInteractions', () => {
|
||||
|
||||
@ -79,7 +79,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
|
||||
@ -15,7 +13,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export default {
|
||||
|
||||
@ -33,7 +33,7 @@ describe('Badges', () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -100,6 +100,7 @@ describe('Badges', () => {
|
||||
id
|
||||
badgeVerification {
|
||||
id
|
||||
isDefault
|
||||
}
|
||||
badgeTrophies {
|
||||
id
|
||||
@ -204,7 +205,7 @@ describe('Badges', () => {
|
||||
data: {
|
||||
setVerificationBadge: {
|
||||
id: 'regular-user-id',
|
||||
badgeVerification: { id: 'verification_moderator' },
|
||||
badgeVerification: { id: 'verification_moderator', isDefault: false },
|
||||
badgeTrophies: [],
|
||||
},
|
||||
},
|
||||
@ -226,7 +227,7 @@ describe('Badges', () => {
|
||||
data: {
|
||||
setVerificationBadge: {
|
||||
id: 'regular-user-id',
|
||||
badgeVerification: { id: 'verification_admin' },
|
||||
badgeVerification: { id: 'verification_admin', isDefault: false },
|
||||
badgeTrophies: [],
|
||||
},
|
||||
},
|
||||
@ -255,7 +256,7 @@ describe('Badges', () => {
|
||||
data: {
|
||||
setVerificationBadge: {
|
||||
id: 'regular-user-2-id',
|
||||
badgeVerification: { id: 'verification_moderator' },
|
||||
badgeVerification: { id: 'verification_moderator', isDefault: false },
|
||||
badgeTrophies: [],
|
||||
},
|
||||
},
|
||||
@ -299,6 +300,7 @@ describe('Badges', () => {
|
||||
id
|
||||
badgeVerification {
|
||||
id
|
||||
isDefault
|
||||
}
|
||||
badgeTrophies {
|
||||
id
|
||||
@ -403,7 +405,7 @@ describe('Badges', () => {
|
||||
data: {
|
||||
rewardTrophyBadge: {
|
||||
id: 'regular-user-id',
|
||||
badgeVerification: null,
|
||||
badgeVerification: { id: 'default_verification', isDefault: true },
|
||||
badgeTrophies: [{ id: 'trophy_rhino' }],
|
||||
},
|
||||
},
|
||||
@ -522,24 +524,30 @@ describe('Badges', () => {
|
||||
beforeEach(async () => {
|
||||
await regularUser.relateTo(badge, 'rewarded')
|
||||
await regularUser.relateTo(verification, 'verifies')
|
||||
await regularUser.relateTo(badge, 'selected', { slot: 6 })
|
||||
})
|
||||
|
||||
const revokeBadgeMutation = gql`
|
||||
mutation ($badgeId: ID!, $userId: ID!) {
|
||||
revokeBadge(badgeId: $badgeId, userId: $userId) {
|
||||
id
|
||||
badgeVerification {
|
||||
id
|
||||
}
|
||||
badgeTrophies {
|
||||
id
|
||||
}
|
||||
badgeVerification {
|
||||
id
|
||||
isDefault
|
||||
}
|
||||
badgeTrophiesSelected {
|
||||
id
|
||||
isDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('check test setup', () => {
|
||||
it('user has one badge', async () => {
|
||||
it('user has one badge and has it selected', async () => {
|
||||
authenticatedUser = regularUser.toJson()
|
||||
const userQuery = gql`
|
||||
{
|
||||
@ -548,11 +556,68 @@ describe('Badges', () => {
|
||||
badgeTrophies {
|
||||
id
|
||||
}
|
||||
badgeVerification {
|
||||
id
|
||||
isDefault
|
||||
}
|
||||
badgeTrophiesSelected {
|
||||
id
|
||||
isDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const expected = {
|
||||
data: { User: [{ badgeTrophiesCount: 1, badgeTrophies: [{ id: 'trophy_rhino' }] }] },
|
||||
data: {
|
||||
User: [
|
||||
{
|
||||
badgeTrophiesCount: 1,
|
||||
badgeTrophies: [{ id: 'trophy_rhino' }],
|
||||
badgeVerification: {
|
||||
id: 'verification_moderator',
|
||||
isDefault: false,
|
||||
},
|
||||
badgeTrophiesSelected: [
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'trophy_rhino',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
}
|
||||
await expect(query({ query: userQuery })).resolves.toMatchObject(expected)
|
||||
@ -596,8 +661,46 @@ describe('Badges', () => {
|
||||
data: {
|
||||
revokeBadge: {
|
||||
id: 'regular-user-id',
|
||||
badgeVerification: { id: 'verification_moderator' },
|
||||
badgeVerification: { id: 'verification_moderator', isDefault: false },
|
||||
badgeTrophies: [],
|
||||
badgeTrophiesSelected: [
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -610,8 +713,46 @@ describe('Badges', () => {
|
||||
data: {
|
||||
revokeBadge: {
|
||||
id: 'regular-user-id',
|
||||
badgeVerification: { id: 'verification_moderator' },
|
||||
badgeVerification: { id: 'verification_moderator', isDefault: false },
|
||||
badgeTrophies: [],
|
||||
badgeTrophiesSelected: [
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -631,8 +772,46 @@ describe('Badges', () => {
|
||||
data: {
|
||||
revokeBadge: {
|
||||
id: 'regular-user-id',
|
||||
badgeVerification: null,
|
||||
badgeVerification: { id: 'default_verification', isDefault: true },
|
||||
badgeTrophies: [{ id: 'trophy_rhino' }],
|
||||
badgeTrophiesSelected: [
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'trophy_rhino',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -659,8 +838,46 @@ describe('Badges', () => {
|
||||
data: {
|
||||
revokeBadge: {
|
||||
id: 'regular-user-id',
|
||||
badgeVerification: null,
|
||||
badgeVerification: { id: 'default_verification', isDefault: true },
|
||||
badgeTrophies: [{ id: 'trophy_rhino' }],
|
||||
badgeTrophiesSelected: [
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'trophy_rhino',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'default_trophy',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
|
||||
@ -6,6 +6,22 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
export const defaultTrophyBadge = {
|
||||
id: 'default_trophy',
|
||||
type: 'trophy',
|
||||
icon: '/img/badges/default_trophy.svg',
|
||||
description: '',
|
||||
createdAt: '',
|
||||
}
|
||||
|
||||
export const defaultVerificationBadge = {
|
||||
id: 'default_verification',
|
||||
type: 'verification',
|
||||
icon: '/img/badges/default_verification.svg',
|
||||
description: '',
|
||||
createdAt: '',
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Badge: async (object, args, context, resolveInfo) =>
|
||||
@ -103,8 +119,10 @@ export default {
|
||||
const response = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})
|
||||
OPTIONAL MATCH (badge:Badge {id: $badgeId})-[relation:REWARDED|VERIFIES]->(user)
|
||||
DELETE relation
|
||||
OPTIONAL MATCH (badge:Badge {id: $badgeId})-[rewarded:REWARDED|VERIFIES]->(user)
|
||||
OPTIONAL MATCH (user)-[selected:SELECTED]->(badge)
|
||||
DELETE rewarded
|
||||
DELETE selected
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{
|
||||
@ -123,4 +141,8 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
Badge: {
|
||||
isDefault: async (parent, _params, _context, _resolveInfo) =>
|
||||
[defaultTrophyBadge.id, defaultVerificationBadge.id].includes(parent.id),
|
||||
},
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -42,7 +42,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('donations', () => {
|
||||
|
||||
@ -36,7 +36,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -110,11 +110,14 @@ describe('AddEmailAddress', () => {
|
||||
|
||||
it('connects `UnverifiedEmailAddress` to the authenticated user', async () => {
|
||||
await mutate({ mutation, variables })
|
||||
const result = await neode.cypher(`
|
||||
const result = await neode.cypher(
|
||||
`
|
||||
MATCH(u:User)-[:PRIMARY_EMAIL]->(:EmailAddress {email: "user@example.org"})
|
||||
MATCH(u:User)<-[:BELONGS_TO]-(e:UnverifiedEmailAddress {email: "new-email@example.org"})
|
||||
RETURN e
|
||||
`)
|
||||
`,
|
||||
{},
|
||||
)
|
||||
const email = neode.hydrateFirst(result, 'e', neode.model('UnverifiedEmailAddress'))
|
||||
await expect(email.toJson()).resolves.toMatchObject({
|
||||
email: 'new-email@example.org',
|
||||
@ -257,10 +260,13 @@ describe('VerifyEmailAddress', () => {
|
||||
|
||||
it('connects the new `EmailAddress` as PRIMARY', async () => {
|
||||
await mutate({ mutation, variables })
|
||||
const result = await neode.cypher(`
|
||||
const result = await neode.cypher(
|
||||
`
|
||||
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"})
|
||||
RETURN e
|
||||
`)
|
||||
`,
|
||||
{},
|
||||
)
|
||||
const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
|
||||
await expect(email.toJson()).resolves.toMatchObject({
|
||||
email: 'to-be-verified@example.org',
|
||||
@ -272,13 +278,13 @@ describe('VerifyEmailAddress', () => {
|
||||
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "user@example.org"})
|
||||
RETURN e
|
||||
`
|
||||
let result = await neode.cypher(cypherStatement)
|
||||
let result = await neode.cypher(cypherStatement, {})
|
||||
let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
|
||||
await expect(email.toJson()).resolves.toMatchObject({
|
||||
email: 'user@example.org',
|
||||
})
|
||||
await mutate({ mutation, variables })
|
||||
result = await neode.cypher(cypherStatement)
|
||||
result = await neode.cypher(cypherStatement, {})
|
||||
email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
|
||||
await expect(email).toBe(false)
|
||||
})
|
||||
@ -288,13 +294,13 @@ describe('VerifyEmailAddress', () => {
|
||||
MATCH(u:User {id: "567"})<-[:BELONGS_TO]-(e:EmailAddress {email: "user@example.org"})
|
||||
RETURN e
|
||||
`
|
||||
let result = await neode.cypher(cypherStatement)
|
||||
let result = await neode.cypher(cypherStatement, {})
|
||||
let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
|
||||
await expect(email.toJson()).resolves.toMatchObject({
|
||||
email: 'user@example.org',
|
||||
})
|
||||
await mutate({ mutation, variables })
|
||||
result = await neode.cypher(cypherStatement)
|
||||
result = await neode.cypher(cypherStatement, {})
|
||||
email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
|
||||
await expect(email).toBe(false)
|
||||
})
|
||||
@ -319,10 +325,13 @@ describe('VerifyEmailAddress', () => {
|
||||
|
||||
it('connects the new `EmailAddress` as PRIMARY', async () => {
|
||||
await mutate({ mutation, variables })
|
||||
const result = await neode.cypher(`
|
||||
const result = await neode.cypher(
|
||||
`
|
||||
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"})
|
||||
RETURN e
|
||||
`)
|
||||
`,
|
||||
{},
|
||||
)
|
||||
const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
|
||||
await expect(email.toJson()).resolves.toMatchObject({
|
||||
email: 'to-be-verified@example.org',
|
||||
|
||||
@ -20,14 +20,17 @@ afterEach(() => {
|
||||
|
||||
let variables = {}
|
||||
|
||||
// eslint-disable-next-line n/no-sync
|
||||
const HumanConnectionOrg = fs.readFileSync(
|
||||
path.join(__dirname, '../../../snapshots/embeds/HumanConnectionOrg.html'),
|
||||
'utf8',
|
||||
)
|
||||
// eslint-disable-next-line n/no-sync
|
||||
const pr3934 = fs.readFileSync(
|
||||
path.join(__dirname, '../../../snapshots/embeds/pr3934.html'),
|
||||
'utf8',
|
||||
)
|
||||
// eslint-disable-next-line n/no-sync
|
||||
const babyLovesCat = fs.readFileSync(
|
||||
path.join(__dirname, '../../../snapshots/embeds/babyLovesCat.html'),
|
||||
'utf8',
|
||||
|
||||
@ -8,6 +8,7 @@ import path from 'node:path'
|
||||
|
||||
import { minimatch } from 'minimatch'
|
||||
|
||||
// eslint-disable-next-line n/no-sync
|
||||
let oEmbedProvidersFile = fs.readFileSync(
|
||||
path.join(__dirname, '../../../../public/providers.json'),
|
||||
'utf8',
|
||||
|
||||
@ -87,8 +87,9 @@ export default async function scrape(url) {
|
||||
throw new ApolloError('Not found', 'NOT_FOUND')
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'link',
|
||||
...output,
|
||||
if (!output.type) {
|
||||
output.type = 'link'
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('Filter Posts', () => {
|
||||
|
||||
@ -76,7 +76,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { getNeode } from '@db/neo4j'
|
||||
|
||||
|
||||
@ -241,7 +241,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('in mode', () => {
|
||||
|
||||
@ -7,8 +7,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
import log from './databaseLogger'
|
||||
|
||||
export const undefinedToNullResolver = (list) => {
|
||||
const resolvers = {}
|
||||
list.forEach((key) => {
|
||||
@ -41,7 +39,6 @@ export default function Resolver(type, options: any = {}) {
|
||||
RETURN related {.*} as related
|
||||
`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
log(result)
|
||||
return result.records.map((r) => r.get('related'))
|
||||
})
|
||||
try {
|
||||
@ -66,7 +63,6 @@ export default function Resolver(type, options: any = {}) {
|
||||
const nodeCondition = condition.replace('this', 'this {id: $id}')
|
||||
const cypher = `${nodeCondition} as ${key}`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
log(result)
|
||||
const [response] = result.records.map((r) => r.get(key))
|
||||
return response
|
||||
})
|
||||
@ -93,7 +89,6 @@ export default function Resolver(type, options: any = {}) {
|
||||
RETURN COUNT(DISTINCT(related)) as count
|
||||
`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
log(result)
|
||||
const [response] = result.records.map((r) => r.get('count').toNumber())
|
||||
return response
|
||||
})
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable import/no-named-as-default */
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import Debug from 'debug'
|
||||
|
||||
const debugCypher = Debug('human-connection:neo4j:cypher')
|
||||
const debugStats = Debug('human-connection:neo4j:stats')
|
||||
|
||||
export default function log(response) {
|
||||
const { counters, resultConsumedAfter, resultAvailableAfter, query } = response.summary
|
||||
const { text, parameters } = query
|
||||
debugCypher('%s', text)
|
||||
debugCypher('%o', parameters)
|
||||
debugStats('%o', counters)
|
||||
debugStats('%o', {
|
||||
resultConsumedAfter: resultConsumedAfter.toNumber(),
|
||||
resultAvailableAfter: resultAvailableAfter.toNumber(),
|
||||
})
|
||||
}
|
||||
@ -25,7 +25,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -83,7 +83,7 @@ describe('deleteImage', () => {
|
||||
return result
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
await expect(someString).toEqual('Hello')
|
||||
@ -106,7 +106,7 @@ describe('deleteImage', () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
// all good
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -198,9 +198,10 @@ describe('mergeImage', () => {
|
||||
|
||||
it('connects resource with image via given image type', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
const result = await neode.cypher(`
|
||||
MATCH(p:Post {id: "p99"})-[:HERO_IMAGE]->(i:Image) RETURN i,p
|
||||
`)
|
||||
const result = await neode.cypher(
|
||||
`MATCH(p:Post {id: "p99"})-[:HERO_IMAGE]->(i:Image) RETURN i,p`,
|
||||
{},
|
||||
)
|
||||
post = neode.hydrateFirst(result, 'p', neode.model('Post'))
|
||||
const image = neode.hydrateFirst(result, 'i', neode.model('Image'))
|
||||
expect(post).toBeTruthy()
|
||||
@ -215,7 +216,7 @@ describe('mergeImage', () => {
|
||||
|
||||
it('sets metadata', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
const image = await neode.first('Image', {})
|
||||
const image = await neode.first<typeof Image>('Image', {}, undefined)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
alt: 'A description of the new image',
|
||||
createdAt: expect.any(String),
|
||||
@ -243,9 +244,13 @@ describe('mergeImage', () => {
|
||||
)
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
const image = await neode.first('Image', { alt: 'This alt text gets overwritten' })
|
||||
const image = await neode.first<typeof Image>(
|
||||
'Image',
|
||||
{ alt: 'This alt text gets overwritten' },
|
||||
undefined,
|
||||
)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
alt: 'This alt text gets overwritten',
|
||||
})
|
||||
@ -268,7 +273,7 @@ describe('mergeImage', () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
// all good
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -296,7 +301,7 @@ describe('mergeImage', () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
const image = await neode.first('Image', {})
|
||||
const image = await neode.first<typeof Image>('Image', {}, undefined)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
alt: 'A description of the new image',
|
||||
createdAt: expect.any(String),
|
||||
|
||||
@ -91,7 +91,7 @@ const wrapTransaction = async (wrappedCallback, args, opts) => {
|
||||
})
|
||||
return result
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,6 +152,7 @@ const s3Upload = async ({ createReadStream, uniqueFilename, mimetype }) => {
|
||||
|
||||
const localFileDelete = async (url) => {
|
||||
const location = `public${url}`
|
||||
// eslint-disable-next-line n/no-sync
|
||||
if (existsSync(location)) unlinkSync(location)
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
describe('inviteCodes', () => {
|
||||
|
||||
@ -30,7 +30,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user