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:
Wolfgang Huß 2025-04-29 10:49:50 +02:00
commit aaa3def677
183 changed files with 7513 additions and 1197 deletions

View File

@ -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 }}

View File

@ -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

View File

@ -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)

View File

@ -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
View File

@ -0,0 +1,3 @@
node_modules/
build/
coverage/

View File

@ -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',

View File

@ -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

View File

View File

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

View 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

View 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

View File

@ -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 = {

View File

@ -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(),

View File

@ -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()
}
})()

View File

@ -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'

View File

@ -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

View File

@ -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()
})()

View File

@ -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)
})

View File

@ -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()
}
}
}

View File

@ -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()
}
}

View File

@ -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(

View File

@ -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(

View File

@ -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()
}
}

View File

@ -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 = `

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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 }

View File

@ -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 */

View File

@ -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")

View File

@ -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
}

View File

@ -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

View File

@ -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'),

View File

@ -0,0 +1,6 @@
// eslint-disable-next-line import/no-cycle
import { MiddlewareOrder } from '@middleware/index'
export default (): MiddlewareOrder[] => {
return []
}

View File

@ -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',

View File

@ -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')

View File

@ -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')

View File

@ -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')

View File

@ -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))
}

View File

@ -32,7 +32,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
const createPostMutation = gql`

View File

@ -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',

View File

@ -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',
}),
)
})
})

View File

@ -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',

View File

@ -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',
}),
)
})
})
})
})

View File

@ -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 () => {

View File

@ -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 () => {

View File

@ -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,

View File

@ -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()
}

View File

@ -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 () => {

View File

@ -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

View File

@ -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({

View File

@ -42,7 +42,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
beforeEach(async () => {

View File

@ -202,7 +202,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
describe('softDeleteMiddleware', () => {

View File

@ -46,7 +46,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
describe('middleware/userInteractions', () => {

View File

@ -79,7 +79,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
beforeEach(async () => {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -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

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'
export default {

View File

@ -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,

View File

@ -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),
},
}

View File

@ -30,7 +30,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
beforeEach(async () => {

View File

@ -42,7 +42,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
describe('donations', () => {

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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
}

View File

@ -38,7 +38,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
describe('Filter Posts', () => {

View File

@ -76,7 +76,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
beforeEach(async () => {

View File

@ -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'

View File

@ -241,7 +241,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
describe('in mode', () => {

View File

@ -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
})

View File

@ -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(),
})
}

View File

@ -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),

View File

@ -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)
}

View File

@ -57,7 +57,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
driver.close()
await driver.close()
})
describe('inviteCodes', () => {

View File

@ -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