Merge branch 'master' into chat-message-notification-e2e-tests

This commit is contained in:
mahula 2025-05-05 07:03:43 +02:00 committed by GitHub
commit f7b5ae47d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
309 changed files with 7678 additions and 4352 deletions

View File

@ -57,19 +57,10 @@ updates:
applies-to: version-updates
patterns:
- "*apollo-server*"
babel:
applies-to: version-updates
patterns:
- "@babel*"
metascraper:
applies-to: version-updates
patterns:
- "metascraper*"
typescript:
applies-to: version-updates
patterns:
- "ts*"
- "*types?"
# webapp
- package-ecosystem: docker

View File

@ -3,8 +3,62 @@ name: ocelot.social end-to-end test CI
on: push
jobs:
docker_preparation:
name: Fullstack test preparation
prepare_neo4j_image:
name: Fullstack | prepare neo4j image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Build docker image
run: |
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/neo4j.tar
- name: Cache docker image
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/neo4j.tar
key: ${{ github.run_id }}-e2e-neo4j-cache
prepare_backend_image:
name: Fullstack | prepare backend image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Build docker image
run: |
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
docker save "ocelotsocialnetwork/backend:test" > /tmp/backend.tar
- name: Cache docker image
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/backend.tar
key: ${{ github.run_id }}-e2e-backend-cache
prepare_webapp_image:
name: Fullstack | prepare webapp image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Build docker image
run: |
docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/
docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar
- name: Cache docker image
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache
prepare_cypress:
name: Fullstack | prepare cypress
runs-on: ubuntu-latest
steps:
- name: Checkout code
@ -13,19 +67,8 @@ jobs:
- name: Copy env files
run: |
cp webapp/.env.template webapp/.env
cp frontend/.env.dist frontend/.env
cp backend/.env.test_e2e backend/.env
- name: Build docker images
run: |
mkdir /tmp/images
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/images/neo4j.tar
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
docker save "ocelotsocialnetwork/backend:test" > /tmp/images/backend.tar
docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/
docker save "ocelotsocialnetwork/webapp:test" > /tmp/images/webapp.tar
- name: Install cypress requirements
run: |
wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
@ -35,21 +78,20 @@ jobs:
cd ..
yarn install
- name: Cache docker images
id: cache
- name: Cache docker image
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: |
/opt/cucumber-json-formatter
/home/runner/.cache/Cypress
/home/runner/work/Ocelot-Social/Ocelot-Social
/tmp/images/
key: ${{ github.run_id }}-e2e-preparation-cache
key: ${{ github.run_id }}-e2e-cypress
fullstack_tests:
name: Fullstack tests
name: Fullstack | tests
if: success()
needs: docker_preparation
needs: [prepare_neo4j_image, prepare_backend_image, prepare_webapp_image, prepare_cypress]
runs-on: ubuntu-latest
env:
jobs: 8
@ -58,26 +100,42 @@ jobs:
# run copies of the current job in parallel
job: [1, 2, 3, 4, 5, 6, 7, 8]
steps:
- name: Restore cache
- name: Restore cypress cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
id: cache
with:
path: |
/opt/cucumber-json-formatter
/home/runner/.cache/Cypress
/home/runner/work/Ocelot-Social/Ocelot-Social
/tmp/images/
key: ${{ github.run_id }}-e2e-preparation-cache
fail-on-cache-miss: true
key: ${{ github.run_id }}-e2e-cypress
restore-keys: ${{ github.run_id }}-e2e-cypress
- name: Restore neo4j cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/neo4j.tar
key: ${{ github.run_id }}-e2e-neo4j-cache
- name: Restore backend cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/backend.tar
key: ${{ github.run_id }}-e2e-backend-cache
- name: Restore webapp cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache
- name: Boot up test system | docker compose
run: |
chmod +x /opt/cucumber-json-formatter
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
docker load < /tmp/images/neo4j.tar
docker load < /tmp/images/backend.tar
docker load < /tmp/images/webapp.tar
docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend mailserver --build
docker load < /tmp/neo4j.tar
docker load < /tmp/backend.tar
docker load < /tmp/webapp.tar
docker compose -f docker-compose.yml -f docker-compose.test.yml up --build --detach --no-deps webapp neo4j backend mailserver
sleep 90s
- name: Full stack tests | run tests
@ -98,17 +156,24 @@ jobs:
name: ocelot-e2e-test-report-pr${{ needs.docker_preparation.outputs.pr-number }}
path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report
cleanup:
name: Cleanup
needs: [docker_preparation, fullstack_tests]
cleanup_cache:
name: Cleanup Cache
needs: fullstack_tests
runs-on: ubuntu-latest
permissions: write-all
continue-on-error: true
steps:
- name: Delete cache
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Full stack tests | cleanup cache
run: |
gh extension install actions/gh-actions-cache
KEY="${{ github.run_id }}-e2e-preparation-cache"
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm
cacheKeys=$(gh cache list --json key --jq '.[] | select(.key | startswith("${{ github.run_id }}-e2e-")) | .key')
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeys
do
gh cache delete "$cacheKey"
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }}
steps:
- uses: amannn/action-semantic-pull-request@04501d43b574e4c1d23c629ffe4dcec27acfdeff # v5.5.3
- uses: amannn/action-semantic-pull-request@335288255954904a41ddda8947c8f2c844b8bfeb # v5.5.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View File

@ -1,2 +0,0 @@
coverage:
range: "60...100"

View File

@ -9,7 +9,7 @@ CLIENT_URI=http://localhost:3000
# E-Mail default settings
EMAIL_SUPPORT="devops@ocelot.social"
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
SMTP_HOST=localhost
SMTP_HOST=mailserver
SMTP_PORT=1025
SMTP_IGNORE_TLS=true
SMTP_MAX_CONNECTIONS=5

View File

@ -133,7 +133,7 @@ module.exports = {
'error',
{ allowModules: ['apollo-server-testing', 'rosie', '@faker-js/faker', 'ts-jest'] },
], // part of n/recommended
// 'n/no-unpublished-require': 'error', // part of n/recommended
'n/no-unpublished-require': ['error', { allowModules: ['ts-jest', 'require-json5'] }], // part of n/recommended
// 'n/no-unsupported-features/es-builtins': 'error', // part of n/recommended
// 'n/no-unsupported-features/es-syntax': 'error', // part of n/recommended
// 'n/no-unsupported-features/node-builtins': 'error', // part of n/recommended
@ -204,7 +204,6 @@ module.exports = {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
// eslint-disable-next-line camelcase
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},
},

View File

@ -1,3 +0,0 @@
{
"schemaPath": "./src/schema.graphql"
}

View File

@ -1,4 +1,4 @@
FROM node:20.12.1-alpine3.19 AS base
FROM node:23.11.0-alpine AS base
LABEL org.label-schema.name="ocelot.social:backend"
LABEL org.label-schema.description="Backend of the Social Network Software ocelot.social"
LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md"
@ -10,7 +10,7 @@ LABEL maintainer="devops@ocelot.social"
ENV NODE_ENV="production"
ENV PORT="4000"
EXPOSE ${PORT}
RUN apk --no-cache add git python3 make g++ bash
RUN apk --no-cache add git python3 make g++ bash linux-headers
RUN mkdir -p /app
WORKDIR /app
CMD ["/bin/bash", "-c", "yarn run start"]

View File

@ -1,15 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
]
],
"plugins": [
"@babel/plugin-proposal-throw-expressions"
]
}

View File

@ -1,6 +1,7 @@
/* eslint-disable import/no-commonjs */
const { pathsToModuleNameMapper } = require('ts-jest')
const requireJSON5 = require('require-json5')
const { pathsToModuleNameMapper } = require('ts-jest')
const { compilerOptions } = requireJSON5('./tsconfig.json')
module.exports = {

View File

@ -10,9 +10,9 @@
"scripts": {
"start": "node build/src/",
"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 --report-unused-disable-directives --ext .js,.ts .",
"dev": "nodemon --exec ts-node --require tsconfig-paths/register src/index.ts -e js,ts,gql",
"dev:debug": "nodemon --exec node --inspect=0.0.0.0:9229 build/src/index.js -e js,ts,gql",
"lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts,.cjs .",
"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",
@ -27,30 +27,16 @@
"prod:db:data:branding": "node build/src/db/data-branding.js"
},
"dependencies": {
"@babel/cli": "~7.27.0",
"@babel/core": "^7.26.10",
"@babel/node": "~7.26.0",
"@babel/plugin-proposal-throw-expressions": "^7.25.9",
"@babel/preset-env": "~7.26.9",
"@babel/register": "^7.23.7",
"@sentry/node": "^5.15.4",
"apollo-cache-inmemory": "~1.6.6",
"apollo-client": "~2.6.10",
"apollo-link-context": "~1.0.20",
"apollo-link-http": "~1.5.17",
"apollo-server": "~2.14.2",
"apollo-server-express": "^2.14.2",
"aws-sdk": "^2.1692.0",
"babel-core": "~7.0.0-0",
"babel-eslint": "~10.1.0",
"babel-jest": "~29.7.0",
"babel-plugin-transform-runtime": "^6.23.0",
"bcryptjs": "~3.0.2",
"body-parser": "^1.20.3",
"cheerio": "~1.0.0",
"cors": "~2.8.5",
"cross-env": "~7.0.3",
"dotenv": "~16.5.0",
"email-templates": "^12.0.2",
"express": "^5.1.0",
"graphql": "^14.6.0",
"graphql-middleware": "~4.0.2",
@ -92,10 +78,11 @@
"node-fetch": "^2.7.0",
"nodemailer": "^6.10.1",
"nodemailer-html-to-text": "^3.2.0",
"preview-email": "^3.1.0",
"pug": "^3.0.3",
"request": "~2.88.2",
"sanitize-html": "~2.16.0",
"slug": "~9.1.0",
"subscriptions-transport-ws": "^0.9.19",
"trunc-html": "~1.1.2",
"uuid": "~9.0.1",
"validator": "^13.15.0",
@ -104,9 +91,11 @@
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
"@faker-js/faker": "9.7.0",
"@types/email-templates": "^10.0.4",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.16",
"@types/node": "^22.15.2",
"@types/node": "^22.15.3",
"@types/slug": "^5.0.9",
"@types/uuid": "~9.0.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
@ -135,7 +124,10 @@
},
"resolutions": {
"**/**/fs-capacitor": "^6.2.0",
"**/graphql-upload": "^11.0.0"
"**/graphql-upload": "^11.0.0",
"**/strip-ansi": "6.0.1",
"**/string-width": "4.2.0",
"**/wrap-ansi": "7.0.0"
},
"engines": {
"node": ">=20.12.1"

View File

@ -15,7 +15,8 @@
<path
fill="#333"
d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"
id="path1" />
id="path1"
style="fill:#868383;fill-opacity:1" />
<g
fill="#ffffff"
id="g2"

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1 +1,45 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#AD245D" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><path d="M35.01 367.726c-.08-21.169-.205-53.162 21.257-71.332 3.817-3.253 9.93-7.497 17.321-9.224 2.575-.523 4.956-.756 7.262-.979 4.438-.431 8.27-.804 12.054-2.9l4.954-2.846c9.87-5.655 19.194-10.996 28.226-17.377 5.085-3.632 6.726-15.73 6.095-25.428-.214-2.792-1.893-5.7-3.67-8.777-1.097-1.901-2.232-3.867-3.065-5.916l-.073-.199a56.976 56.976 0 0 1-.422-1.443c-1.195-4.205-1.933-6.378-2.386-7.476-7.029-.944-11.8-8.647-12.888-21.006l-.031-.557c-.645-12.785.808-16.13 2.316-17.716.24-.254.505-.475.783-.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.458 11.355 1.362 25.63 6.228 41.679 30.27 40.062 59.227.53.251 1.018.61 1.44 1.066 2.752 2.964 2.47 10.97 2.22 14.276l-.024.41c-.335 5.236-.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-.7 2.416-1.645 4.866-2.559 7.235-1.752 4.538-3.407 8.827-3.54 13.244-.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.855 3.166 1.692 4.654 2.517 9.28 5.052 16.07 7.915 25.309 8.557 9.118.849 18.056 5.193 24.754 11.97.736.641 1.82 1.744 3.694 3.648 4.416 4.492 4.416 4.492 4.426 5.852l.007.758c10.783 17.702 11.14 40.656 11.415 58.169l.05 3.28-3.278.028c-42.05.363-84.058.677-126.058.993-42.12.314-84.232.632-126.367.994l-3.273.029-.014-3.274zM329.011 135.763a5.232 5.232 0 0 0-5.223 5.23 5.232 5.232 0 0 0 5.223 5.23 5.236 5.236 0 0 0 5.231-5.23 5.236 5.236 0 0 0-5.23-5.23m0 40.237C309.705 176 294 160.297 294 140.993 294 121.698 309.706 106 329.011 106 348.303 106 364 121.698 364 140.993 364 160.297 348.303 176 329.011 176" fill="#FFF"/><path d="M330.511 101C308.173 101 290 119.164 290 141.492 290 163.828 308.173 182 330.511 182 352.836 182 371 163.828 371 141.492 371 119.164 352.836 101 330.511 101m0 51.022c5.823 0 10.531-4.716 10.531-10.53a10.517 10.517 0 0 0-10.53-10.529 10.51 10.51 0 0 0-10.523 10.529c0 5.814 4.7 10.53 10.522 10.53m0-40.496c16.563 0 29.963 13.406 29.963 29.966 0 16.555-13.4 29.982-29.963 29.982-16.555 0-29.985-13.427-29.985-29.982 0-16.56 13.43-29.966 29.985-29.966" fill="#AD245D"/><path d="M331 106.209c-20.305 0-36.825 16.06-36.825 35.799 0 19.747 16.52 35.813 36.825 35.813 20.306 0 36.827-16.066 36.827-35.813 0-19.74-16.521-35.8-36.827-35.8zM314.287 215l-4.11-21.345c-.324-.129-.648-.265-.972-.404l-18.012 12.169-23.607-23.609 12.186-18.009a63.31 63.31 0 0 1-.403-.968L258 158.712v-33.383l21.361-4.13c.131-.327.267-.652.407-.979l-12.18-18.025 23.608-23.612 18.015 12.198c.322-.137.643-.27.964-.4L314.287 69h33.416l4.13 21.387c.319.13.638.26.956.396l18.024-12.2 23.608 23.612-12.186 18.031c.139.324.273.648.402.971L404 125.33v33.381l-21.37 4.124c-.13.32-.262.64-.398.96l12.19 18.017-23.606 23.609-18.021-12.171c-.32.137-.642.27-.964.402L347.701 215h-33.414z" fill="#FFF"/><path d="M330 171.448c-17.342 0-31.45-13.656-31.45-30.44 0-16.778 14.108-30.427 31.45-30.427 17.341 0 31.449 13.649 31.449 30.426 0 16.785-14.108 30.441-31.45 30.441zM350.979 63h-41.97l-1.64 8.517-1.953 10.156-8.55-5.788-7.18-4.862-6.132 6.132-17.399 17.4-6.126 6.126 4.85 7.178 5.8 8.583-10.173 1.966-8.506 1.646v41.932l8.51 1.643 10.16 1.962-5.787 8.553-4.858 7.179 6.13 6.13 17.399 17.399 6.126 6.126 7.177-4.85 8.56-5.782 1.954 10.141 1.64 8.513H350.975l1.645-8.507 1.964-10.15 8.566 5.787 7.178 4.846 6.124-6.124 17.4-17.399 6.13-6.13-4.858-7.18-5.788-8.554 10.153-1.96 8.51-1.643v-41.932l-8.507-1.646-10.165-1.965 5.8-8.584 4.85-7.178-6.125-6.126-17.4-17.4-6.13-6.13-7.18 4.858-8.558 5.792-1.964-10.166L350.98 63zm-20.98 118.948c23.176 0 41.95-18.318 41.95-40.94 0-22.607-18.774-40.927-41.95-40.927-23.174 0-41.948 18.32-41.948 40.926 0 22.623 18.774 40.941 41.949 40.941zM342.313 73.5l3.855 19.963a47.184 47.184 0 0 1 6.037 2.502l16.824-11.386 17.4 17.4-11.362 16.818a53.171 53.171 0 0 1 2.502 6.066l19.932 3.855v24.601l-19.932 3.848a56.644 56.644 0 0 1-2.502 6.066l11.362 16.795-17.4 17.4-16.824-11.364a44.931 44.931 0 0 1-6.037 2.504l-3.855 19.932H317.68l-3.84-19.932a43.821 43.821 0 0 1-6.043-2.504l-16.818 11.364-17.4-17.4 11.364-16.795a53.759 53.759 0 0 1-2.51-6.066l-19.933-3.848v-24.601l19.933-3.855a50.617 50.617 0 0 1 2.51-6.066l-11.364-16.818 17.4-17.4 16.818 11.386a45.957 45.957 0 0 1 6.043-2.502l3.84-19.963h24.632z" fill="#AD245D"/></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="513"
height="444"
version="1.1"
id="svg5"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5" />
<g
fill="none"
fill-rule="evenodd"
id="g5">
<path
fill="#ad245d"
d="M 384.5,0.297 512.325,221.9 384.325,443.602 128.5,443.704 0.675,222.1 128.675,0.4 Z"
id="path1"
style="display:inline;fill:#e67919;fill-opacity:1" />
<path
d="m 83.051688,348.226 c -0.08,-21.169 -0.205,-53.162 21.257002,-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.367002,0.994 l -3.273,0.029 z"
id="path2-5"
style="display:inline;fill:#ffffff" />
<path
d="m 351.97006,182.43811 c -2.88526,0.005 -5.2219,2.34474 -5.223,5.23 0.001,2.88526 2.33774,5.22504 5.223,5.23 2.88747,-0.003 5.22769,-2.34253 5.231,-5.23 -0.003,-2.88708 -2.34292,-5.22669 -5.23,-5.23 m 0,40.237 c -19.307,0 -35.012,-15.703 -35.012,-35.007 0,-19.295 15.706,-34.993 35.011,-34.993 19.292,0 34.989,15.698 34.989,34.993 0,19.304 -15.697,35.007 -34.989,35.007"
fill="#ffffff"
id="path2"
style="display:inline" />
<path
d="m 351.97006,147.67511 c -22.338,0 -40.511,18.164 -40.511,40.492 0,22.336 18.173,40.508 40.511,40.508 22.325,0 40.489,-18.172 40.489,-40.508 0,-22.328 -18.164,-40.492 -40.489,-40.492 m 0,51.022 c 5.823,0 10.531,-4.716 10.531,-10.53 a 10.517,10.517 0 0 0 -10.53,-10.529 10.51,10.51 0 0 0 -10.523,10.529 c 0,5.814 4.7,10.53 10.522,10.53 m 0,-40.496 c 16.563,0 29.963,13.406 29.963,29.966 0,16.555 -13.4,29.982 -29.963,29.982 -16.555,0 -29.985,-13.427 -29.985,-29.982 0,-16.56 13.43,-29.966 29.985,-29.966"
fill="#ad245d"
id="path3"
style="display:inline;fill:#e67919;fill-opacity:1" />
<path
d="m 351.95906,152.88411 c -20.305,0 -36.825,16.06 -36.825,35.799 0,19.747 16.52,35.813 36.825,35.813 20.306,0 36.827,-16.066 36.827,-35.813 0,-19.74 -16.521,-35.8 -36.827,-35.8 z m -16.713,108.791 -4.11,-21.345 c -0.324,-0.129 -0.648,-0.265 -0.972,-0.404 l -18.012,12.169 -23.607,-23.609 12.186,-18.009 a 63.31,63.31 0 0 1 -0.403,-0.968 l -21.369,-4.122 v -33.383 l 21.361,-4.13 c 0.131,-0.327 0.267,-0.652 0.407,-0.979 l -12.18,-18.025 23.608,-23.612 18.015,12.198 c 0.322,-0.137 0.643,-0.27 0.964,-0.4 l 4.112,-21.381 h 33.416 l 4.13,21.387 c 0.319,0.13 0.638,0.26 0.956,0.396 l 18.024,-12.2 23.608,23.612 -12.186,18.031 c 0.139,0.324 0.273,0.648 0.402,0.971 l 21.363,4.133 v 33.381 l -21.37,4.124 c -0.13,0.32 -0.262,0.64 -0.398,0.96 l 12.19,18.017 -23.606,23.609 -18.021,-12.171 c -0.32,0.137 -0.642,0.27 -0.964,0.402 l -4.13,21.348 z"
fill="#ffffff"
id="path4"
style="display:inline" />
<path
d="m 351.95956,218.12311 c -17.342,0 -31.45,-13.656 -31.45,-30.44 0,-16.778 14.108,-30.427 31.45,-30.427 17.341,0 31.449,13.649 31.449,30.426 0,16.785 -14.108,30.441 -31.45,30.441 z m 20.979,-108.448 h -41.97 l -1.64,8.517 -1.953,10.156 -8.55,-5.788 -7.18,-4.862 -6.132,6.132 -17.399,17.4 -6.126,6.126 4.85,7.178 5.8,8.583 -10.173,1.966 -8.506,1.646 v 41.932 l 8.51,1.643 10.16,1.962 -5.787,8.553 -4.858,7.179 6.13,6.13 17.399,17.399 6.126,6.126 7.177,-4.85 8.56,-5.782 1.954,10.141 1.64,8.513 h 41.964 l 1.645,-8.507 1.964,-10.15 8.566,5.787 7.178,4.846 6.124,-6.124 17.4,-17.399 6.13,-6.13 -4.858,-7.18 -5.788,-8.554 10.153,-1.96 8.51,-1.643 v -41.932 l -8.507,-1.646 -10.165,-1.965 5.8,-8.584 4.85,-7.178 -6.125,-6.126 -17.4,-17.4 -6.13,-6.13 -7.18,4.858 -8.558,5.792 -1.964,-10.166 -1.64,-8.509 z m -20.98,118.948 c 23.176,0 41.95,-18.318 41.95,-40.94 0,-22.607 -18.774,-40.927 -41.95,-40.927 -23.174,0 -41.948,18.32 -41.948,40.926 0,22.623 18.774,40.941 41.949,40.941 z m 12.314,-108.448 3.855,19.963 a 47.184,47.184 0 0 1 6.037,2.502 l 16.824,-11.386 17.4,17.4 -11.362,16.818 a 53.171,53.171 0 0 1 2.502,6.066 l 19.932,3.855 v 24.601 l -19.932,3.848 a 56.644,56.644 0 0 1 -2.502,6.066 l 11.362,16.795 -17.4,17.4 -16.824,-11.364 a 44.931,44.931 0 0 1 -6.037,2.504 l -3.855,19.932 h -24.633 l -3.84,-19.932 a 43.821,43.821 0 0 1 -6.043,-2.504 l -16.818,11.364 -17.4,-17.4 11.364,-16.795 a 53.759,53.759 0 0 1 -2.51,-6.066 l -19.933,-3.848 v -24.601 l 19.933,-3.855 a 50.617,50.617 0 0 1 2.51,-6.066 l -11.364,-16.818 17.4,-17.4 16.818,11.386 a 45.957,45.957 0 0 1 6.043,-2.502 l 3.84,-19.963 h 24.632 z"
fill="#ad245d"
id="path5"
style="display:inline;fill:#e67919;fill-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1 +1,38 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#AD245D" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><g fill="#FFF"><path d="M35.01 367.726c-.08-21.169-.205-53.162 21.257-71.332 3.817-3.253 9.93-7.497 17.321-9.224 2.575-.523 4.956-.756 7.262-.979 4.438-.431 8.27-.804 12.054-2.9l4.954-2.846c9.87-5.655 19.194-10.996 28.226-17.377 5.085-3.632 6.726-15.73 6.095-25.428-.214-2.792-1.893-5.7-3.67-8.777-1.097-1.901-2.232-3.867-3.065-5.916l-.073-.199a56.976 56.976 0 0 1-.422-1.443c-1.195-4.205-1.933-6.378-2.386-7.476-7.029-.944-11.8-8.647-12.888-21.006l-.031-.557c-.645-12.785.808-16.13 2.316-17.716.24-.254.505-.475.783-.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.458 11.355 1.362 25.63 6.228 41.679 30.27 40.062 59.227.53.251 1.018.61 1.44 1.066 2.752 2.964 2.47 10.97 2.22 14.276l-.024.41c-.335 5.236-.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-.7 2.416-1.645 4.866-2.559 7.235-1.752 4.538-3.407 8.827-3.54 13.244-.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.855 3.166 1.692 4.654 2.517 9.28 5.052 16.07 7.915 25.309 8.557 9.118.849 18.056 5.193 24.754 11.97.736.641 1.82 1.744 3.694 3.648 4.416 4.492 4.416 4.492 4.426 5.852l.007.758c10.783 17.702 11.14 40.656 11.415 58.169l.05 3.28-3.278.028c-42.05.363-84.058.677-126.058.993-42.12.314-84.232.632-126.367.994l-3.273.029-.014-3.274z"/><text font-family="Impact" font-size="118" font-style="condensed" font-weight="700" transform="translate(1 -1)"><tspan x="256" y="208">&lt;/&gt;</tspan></text></g></g></svg>
<?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="#AD245D"
d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"
id="path1"
style="fill:#e67919;fill-opacity:1" />
<path
d="m 80.877371,348.226 c -0.08,-21.169 -0.205,-53.162 21.256999,-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.366999,0.994 l -3.273,0.029 z"
id="path2-5"
style="display:inline;fill:#ffffff" />
<text
font-family="Impact"
font-size="118px"
font-style="condensed"
font-weight="700"
id="text2"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;font-family:Monospace;-inkscape-font-specification:Monospace;display:inline;fill:#ffffff"
x="6.8672509"
y="17"><tspan
x="262.86725"
y="225"
id="tspan2"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;font-family:Monospace;-inkscape-font-specification:Monospace;fill:#ffffff">&lt;/&gt;</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1 +1,29 @@
<svg width="512" height="444" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#AD245D" d="M384 .297L511.392 221.65l-128 221.702-255.392.352L.608 222.35 128.608.65z"/><g fill="#FFF"><path d="M34.944 367.726c-.081-21.169-.205-53.162 21.215-71.332 3.81-3.253 9.91-7.497 17.288-9.224 2.57-.523 4.946-.756 7.247-.979 4.43-.431 8.254-.804 12.03-2.9l4.945-2.846c9.851-5.655 19.157-10.996 28.171-17.377 5.075-3.632 6.713-15.73 6.082-25.428-.213-2.792-1.888-5.7-3.662-8.777-1.095-1.901-2.228-3.867-3.059-5.916l-.073-.199a57.061 57.061 0 0 1-.42-1.443c-1.194-4.205-1.93-6.378-2.382-7.476-7.015-.944-11.778-8.647-12.864-21.006l-.03-.557c-.644-12.785.806-16.13 2.31-17.716.241-.254.505-.475.783-.666-1.75-16.051 3.11-32.521 13.331-44.704 9.296-11.079 21.912-17.18 35.524-17.18 3.722 0 7.535.458 11.332 1.362 25.58 6.228 41.597 30.27 39.983 59.227.53.251 1.016.61 1.439 1.066 2.745 2.964 2.464 10.97 2.215 14.276l-.024.41c-.335 5.236-.683 10.65-3.046 15.73-1.736 3.918-4.397 6.242-6.747 8.29-2.391 2.089-4.28 3.735-5.284 6.885-.698 2.416-1.642 4.866-2.554 7.235-1.749 4.538-3.4 8.827-3.534 13.244-.425 10.222 1.17 18.391 4.165 21.359 5.087 5.163 12.977 9.391 19.939 13.121 1.597.855 3.16 1.692 4.645 2.517 9.262 5.052 16.038 7.915 25.259 8.557 9.1.849 18.02 5.193 24.706 11.97.735.641 1.817 1.744 3.687 3.648 4.408 4.492 4.408 4.492 4.417 5.852l.007.758c10.762 17.702 11.12 40.656 11.392 58.169l.05 3.28-3.271.028c-41.968.363-83.894.677-125.812.993-42.038.314-84.067.632-126.12.994l-3.267.029-.013-3.274zM332.387 115.763h15.318v86.734h-15.318v.513l-22.127-17.64v.12h-10.21V211h-22.128v-25.51h-20.424v-52.722h52.762v-.508l22.127-17.065v.568zm34.313 72.093l-7.988-4.591c15.803-27.453 1.717-46.642 1.106-47.443l7.304-5.607c.774.993 18.547 24.675-.422 57.641zm27.361 21.216l-13.866-7.975c27.437-47.66 2.98-80.973 1.918-82.36L394.795 109c1.343 1.723 32.2 42.839-.734 100.072z"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="512"
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="#AD245D"
d="M384 .297L511.392 221.65l-128 221.702-255.392.352L.608 222.35 128.608.65z"
id="path1"
style="fill:#e67919;fill-opacity:1" />
<path
d="m 85.310542,348.226 c -0.08,-21.169 -0.205,-53.162 21.256998,-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.366998,0.994 l -3.273,0.029 z"
id="path2-5"
style="display:inline;fill:#ffffff" />
<path
d="m 349.88573,158.65792 h 15.318 v 86.734 h -15.318 v 0.513 l -22.127,-17.64 v 0.12 h -10.21 v 25.51 h -22.128 v -25.51 h -20.424 v -52.722 h 52.762 v -0.508 l 22.127,-17.065 z m 34.313,72.093 -7.988,-4.591 c 15.803,-27.453 1.717,-46.642 1.106,-47.443 l 7.304,-5.607 c 0.774,0.993 18.547,24.675 -0.422,57.641 z m 27.361,21.216 -13.866,-7.975 c 27.437,-47.66 2.98,-80.973 1.918,-82.36 l 12.682,-9.737 c 1.343,1.723 32.2,42.839 -0.734,100.072 z"
id="path2-1"
style="display:inline;fill:#ffffff" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -25,6 +25,7 @@ const environment = {
DISABLED_MIDDLEWARES: ['test', 'development'].includes(env.NODE_ENV as string)
? (env.DISABLED_MIDDLEWARES?.split(',') ?? [])
: [],
SEND_MAIL: env.NODE_ENV !== 'test',
}
const required = {

View File

@ -0,0 +1,3 @@
export const NOTIFICATION_ADDED = 'NOTIFICATION_ADDED'
export const CHAT_MESSAGE_ADDED = 'CHAT_MESSAGE_ADDED'
export const ROOM_COUNT_UPDATED = 'ROOM_COUNT_UPDATED'

View File

@ -0,0 +1,49 @@
import { getDriver, getNeode } from '@db/neo4j'
import type { Driver } from 'neo4j-driver'
export const query =
(driver: Driver) =>
async ({ query, variables = {} }: { driver; query: string; variables: object }) => {
const session = driver.session()
const result = session.readTransaction(async (transaction) => {
const response = await transaction.run(query, variables)
return response
})
try {
return await result
} finally {
await session.close()
}
}
export const mutate =
(driver: Driver) =>
async ({ query, variables = {} }: { driver; query: string; variables: object }) => {
const session = driver.session()
const result = session.writeTransaction(async (transaction) => {
const response = await transaction.run(query, variables)
return response
})
try {
return await result
} finally {
await session.close()
}
}
export default () => {
const driver = getDriver()
const neode = getNeode()
return {
driver,
neode,
query: query(driver),
mutate: mutate(driver),
}
}

View File

@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { RedisPubSub } from 'graphql-redis-subscriptions'
import { PubSub } from 'graphql-subscriptions'
import Redis from 'ioredis'
import CONFIG from '@config/index'
export default () => {
if (!CONFIG.REDIS_DOMAIN || CONFIG.REDIS_PORT || CONFIG.REDIS_PASSWORD) {
return new PubSub()
}
const options = {
host: CONFIG.REDIS_DOMAIN,
port: CONFIG.REDIS_PORT,
password: CONFIG.REDIS_PASSWORD,
retryStrategy: (times) => {
return Math.min(times * 50, 2000)
},
}
return new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
})
}

View File

@ -10,7 +10,7 @@ import { Factory } from 'rosie'
import slugify from 'slug'
import { v4 as uuid } from 'uuid'
import generateInviteCode from '@schema/resolvers/helpers/generateInviteCode'
import generateInviteCode from '@graphql/resolvers/helpers/generateInviteCode'
import { getDriver, getNeode } from './neo4j'

View File

@ -10,7 +10,7 @@ import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
import { getDriver } from '@db/neo4j'
import normalizeEmail from '@schema/resolvers/helpers/normalizeEmail'
import normalizeEmail from '@graphql/resolvers/helpers/normalizeEmail'
export const description = `
This migration merges duplicate :User and :EmailAddress nodes. It became

View File

@ -6,7 +6,7 @@ import neo4j, { Driver } from 'neo4j-driver'
import Neode from 'neode'
import CONFIG from '@config/index'
import models from '@models/index'
import models from '@db/models/index'
let driver: Driver
const defaultOptions = {

View File

@ -26,6 +26,8 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
throw new Error(`You cannot seed the database in a non-staging and real production environment!`)
}
CONFIG.SEND_MAIL = true
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
;(async function () {

View File

@ -9,155 +9,155 @@ export const trophies = async () => {
trophyBear: await Factory.build('badge', {
id: 'trophy_bear',
type: 'trophy',
description: 'You earned a Bear',
description: 'Has earned a Bear',
icon: '/img/badges/trophy_blue_bear.svg',
}),
trophyPanda: await Factory.build('badge', {
id: 'trophy_panda',
type: 'trophy',
description: 'You earned a Panda',
description: 'Has earned a Panda',
icon: '/img/badges/trophy_blue_panda.svg',
}),
trophyRabbit: await Factory.build('badge', {
id: 'trophy_rabbit',
type: 'trophy',
description: 'You earned a Rabbit',
description: 'Has earned a Rabbit',
icon: '/img/badges/trophy_blue_rabbit.svg',
}),
trophyRacoon: await Factory.build('badge', {
id: 'trophy_racoon',
type: 'trophy',
description: 'You earned a Racoon',
description: 'Has earned a Racoon',
icon: '/img/badges/trophy_blue_racoon.svg',
}),
trophyRhino: await Factory.build('badge', {
id: 'trophy_rhino',
type: 'trophy',
description: 'You earned a Rhino',
description: 'Has earned a Rhino',
icon: '/img/badges/trophy_blue_rhino.svg',
}),
trophyTiger: await Factory.build('badge', {
id: 'trophy_tiger',
type: 'trophy',
description: 'You earned a Tiger',
description: 'Has earned a Tiger',
icon: '/img/badges/trophy_blue_tiger.svg',
}),
trophyTurtle: await Factory.build('badge', {
id: 'trophy_turtle',
type: 'trophy',
description: 'You earned a Turtle',
description: 'Has earned a Turtle',
icon: '/img/badges/trophy_blue_turtle.svg',
}),
trophyWhale: await Factory.build('badge', {
id: 'trophy_whale',
type: 'trophy',
description: 'You earned a Whale',
description: 'Has earned a Whale',
icon: '/img/badges/trophy_blue_whale.svg',
}),
trophyWolf: await Factory.build('badge', {
id: 'trophy_wolf',
type: 'trophy',
description: 'You earned a Wolf',
description: 'Has earned a Wolf',
icon: '/img/badges/trophy_blue_wolf.svg',
}),
// Green Transports
trophyAirship: await Factory.build('badge', {
id: 'trophy_airship',
type: 'trophy',
description: 'You earned an Airship',
description: 'Has earned an Airship',
icon: '/img/badges/trophy_green_airship.svg',
}),
trophyAlienship: await Factory.build('badge', {
id: 'trophy_alienship',
type: 'trophy',
description: 'You earned an Alienship',
description: 'Has earned an Alienship',
icon: '/img/badges/trophy_green_alienship.svg',
}),
trophyBalloon: await Factory.build('badge', {
id: 'trophy_balloon',
type: 'trophy',
description: 'You earned a Balloon',
description: 'Has earned a Balloon',
icon: '/img/badges/trophy_green_balloon.svg',
}),
trophyBigballoon: await Factory.build('badge', {
id: 'trophy_bigballoon',
type: 'trophy',
description: 'You earned a Big Balloon',
description: 'Has earned a Big Balloon',
icon: '/img/badges/trophy_green_bigballoon.svg',
}),
trophyCrane: await Factory.build('badge', {
id: 'trophy_crane',
type: 'trophy',
description: 'You earned a Crane',
description: 'Has earned a Crane',
icon: '/img/badges/trophy_green_crane.svg',
}),
trophyGlider: await Factory.build('badge', {
id: 'trophy_glider',
type: 'trophy',
description: 'You earned a Glider',
description: 'Has earned a Glider',
icon: '/img/badges/trophy_green_glider.svg',
}),
trophyHelicopter: await Factory.build('badge', {
id: 'trophy_helicopter',
type: 'trophy',
description: 'You earned a Helicopter',
description: 'Has earned a Helicopter',
icon: '/img/badges/trophy_green_helicopter.svg',
}),
// Green Animals
trophyBee: await Factory.build('badge', {
id: 'trophy_bee',
type: 'trophy',
description: 'You earned a Bee',
description: 'Has earned a Bee',
icon: '/img/badges/trophy_green_bee.svg',
}),
trophyButterfly: await Factory.build('badge', {
id: 'trophy_butterfly',
type: 'trophy',
description: 'You earned a Butterfly',
description: 'Has earned a Butterfly',
icon: '/img/badges/trophy_green_butterfly.svg',
}),
// Green Plants
trophyFlower: await Factory.build('badge', {
id: 'trophy_flower',
type: 'trophy',
description: 'You earned a Flower',
description: 'Has earned a Flower',
icon: '/img/badges/trophy_green_flower.svg',
}),
trophyLifetree: await Factory.build('badge', {
id: 'trophy_lifetree',
type: 'trophy',
description: 'You earned the tree of life',
description: 'Has earned the tree of life',
icon: '/img/badges/trophy_green_lifetree.svg',
}),
// Green Misc
trophyDoublerainbow: await Factory.build('badge', {
id: 'trophy_doublerainbow',
type: 'trophy',
description: 'You earned the Double Rainbow',
description: 'Has earned the Double Rainbow',
icon: '/img/badges/trophy_green_doublerainbow.svg',
}),
trophyEndrainbow: await Factory.build('badge', {
id: 'trophy_endrainbow',
type: 'trophy',
description: 'You earned the End of the Rainbow',
description: 'Has earned the End of the Rainbow',
icon: '/img/badges/trophy_green_endrainbow.svg',
}),
trophyMagicrainbow: await Factory.build('badge', {
id: 'trophy_magicrainbow',
type: 'trophy',
description: 'You earned the Magic Rainbow',
description: 'Has earned the Magic Rainbow',
icon: '/img/badges/trophy_green_magicrainbow.svg',
}),
trophyStarter: await Factory.build('badge', {
id: 'trophy_starter',
type: 'trophy',
description: 'You earned the Starter Badge',
description: 'Has earned the Starter Badge',
icon: '/img/badges/trophy_green_starter.svg',
}),
trophySuperfounder: await Factory.build('badge', {
id: 'trophy_superfounder',
type: 'trophy',
description: 'You earned the Super Founder Badge',
description: 'Has earned the Super Founder Badge',
icon: '/img/badges/trophy_green_superfounder.svg',
}),
}
@ -169,19 +169,19 @@ export const verification = async () => {
verificationModerator: await Factory.build('badge', {
id: 'verification_moderator',
type: 'verification',
description: 'You are a Moderator',
description: 'Is a Moderator',
icon: '/img/badges/verification_red_moderator.svg',
}),
verificationAdmin: await Factory.build('badge', {
id: 'verification_admin',
type: 'verification',
description: 'You are an Administrator',
description: 'Is an Administrator',
icon: '/img/badges/verification_red_admin.svg',
}),
verificationDeveloper: await Factory.build('badge', {
id: 'verification_developer',
type: 'verification',
description: 'You are a Developer',
description: 'Is a Developer',
icon: '/img/badges/verification_red_developer.svg',
}),
}

View File

@ -0,0 +1,32 @@
import { Integer, Node } from 'neo4j-driver'
export interface UserDbProperties {
allowEmbedIframes: boolean
awaySince?: string
createdAt: string
deleted: boolean
disabled: boolean
emailNotificationsChatMessage?: boolean
emailNotificationsCommentOnObservedPost?: boolean
emailNotificationsFollowingUsers?: boolean
emailNotificationsGroupMemberJoined?: boolean
emailNotificationsGroupMemberLeft?: boolean
emailNotificationsGroupMemberRemoved?: boolean
emailNotificationsGroupMemberRoleChanged?: boolean
emailNotificationsMention?: boolean
emailNotificationsPostInGroup?: boolean
encryptedPassword: string
id: string
lastActiveAt?: string
lastOnlineStatus?: string
locale: string
name: string
role: string
showShoutsPublicly: boolean
slug: string
termsAndConditionsAgreedAt: string
termsAndConditionsAgreedVersion: string
updatedAt: string
}
export type User = Node<Integer, UserDbProperties>

View File

@ -0,0 +1,247 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendChatMessageMail English chat_message template 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="en">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Hello chatReceiver,</h2>
<div class="wrapper">
<div class="content"></div>
<p>you have received a new chat message from <a class="user" href="http://webapp:3000/user/chatSender/chatsender">chatSender</a>.
</p><a class="button" href="http://webapp:3000/chat">Show Chat</a>
<div class="text-block">
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> The ocelot.social Team</p><br>
<p>PS: If you don't want to receive e-mails anymore, change your <a class="settings" href="http://webapp:3000/settings/notifications">notification settings</a>!</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "ocelot.social Notification: New chat message",
"text": "HELLO CHATRECEIVER,
you have received a new chat message from chatSender
[http://webapp:3000/user/chatSender/chatsender].
Show Chat [http://webapp:3000/chat]
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendChatMessageMail German chat_message template 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="de">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Hallo chatReceiver,</h2>
<div class="wrapper">
<div class="content"></div>
<p>du hast eine neue Chat-Nachricht von <a class="user" href="http://webapp:3000/user/chatSender/chatsender">chatSender</a> erhalten.
</p><a class="button" href="http://webapp:3000/chat">Chat anzeigen</a>
<div class="text-block">
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> Dein ocelot.social Team</p><br>
<p>PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine <a class="settings" href="http://webapp:3000/settings/notifications">Benachrichtigungseinstellungen</a>!</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "ocelot.social Benachrichtigung: Neue Chat Nachricht",
"text": "HALLO CHATRECEIVER,
du hast eine neue Chat-Nachricht von chatSender
[http://webapp:3000/user/chatSender/chatsender] erhalten.
Chat anzeigen [http://webapp:3000/chat]
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
{
"notification": "Benachrichtigung",
"subjects": {
"changedGroupMemberRole": "Rolle in Gruppe geändert",
"chatMessage": "Neue Chat Nachricht",
"commentedOnPost": "Neuer Kommentar zu Beitrag",
"followedUserPosted": "Neuer Beitrag von gefolgtem Nutzer",
"mentionedInComment": "Erwähnung in Kommentar",
"mentionedInPost": "Erwähnung in Beitrag",
"removedUserFromGroup": "Aus Gruppe entfernt",
"postInGroup": "Neuer Beitrag in Gruppe",
"userJoinedGroup": "Nutzer tritt Gruppe bei",
"userLeftGroup": "Nutzer verlässt Gruppe"
},
"buttons": {
"viewChat": "Chat anzeigen",
"viewComment": "Kommentar ansehen",
"viewGroup": "Gruppe ansehen",
"viewPost": "Beitrag ansehen"
},
"general": {
"greeting": "Hallo",
"seeYou": "Bis bald bei ",
"yourTeam": " Dein {team} Team",
"settingsHint": "PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine ",
"settingsName": "Benachrichtigungseinstellungen"
},
"changedGroupMemberRole": "deine Rolle in der Gruppe „{groupName}“ wurde geändert. Klicke auf den Knopf, um diese Gruppe zu sehen:",
"chatMessageStart": "du hast eine neue Chat-Nachricht von ",
"chatMessageEnd": " erhalten.",
"commentedOnPost": " hat einen Beitrag den du beobachtest mit dem Titel „{postTitle}“ kommentiert. Klicke auf den Knopf, um diesen Kommentar zu sehen:",
"followedUserPosted": ", ein Nutzer dem du folgst, hat einen neuen Beitrag mit dem Titel „{postTitle}“ geschrieben. Klicke auf den Knopf, um diesen Beitrag zu sehen:",
"mentionedInComment": " hat dich in einem Kommentar zu dem Beitrag mit dem Titel „{postTitle}“ erwähnt. Klicke auf den Knopf, um den Kommentar zu sehen:",
"mentionedInPost": " hat Dich in einem Beitrag mit dem Titel „{postTitle}“ erwähnt. Klicke auf den Knopf, um den Beitrag zu sehen:",
"postInGroup": "jemand hat einen neuen Beitrag mit dem Titel „{postTitle}“ in einer deiner Gruppen geschrieben. Klicke auf den Knopf, um diesen Beitrag zu sehen:",
"removedUserFromGroup": "du wurdest aus der Gruppe „{groupName}“ entfernt.",
"userJoinedGroup": " ist der Gruppe „{groupName}“ beigetreten. Klicke auf den Knopf, um diese Gruppe zu sehen:",
"userLeftGroup": " hat die Gruppe „{groupName}“ verlassen. Klicke auf den Knopf, um diese Gruppe zu sehen:"
}

View File

@ -0,0 +1,39 @@
{
"notification": "Notification",
"subjects": {
"changedGroupMemberRole": "Role in group changed",
"chatMessage": "New chat message",
"commentedOnPost": "New comment on post",
"followedUserPosted": "New post by followd user",
"mentionedInComment": "Mentioned in comment",
"mentionedInPost": "Mentioned in post",
"removedUserFromGroup": "Removed from group",
"postInGroup": "New post in group",
"userJoinedGroup": "User joined group",
"userLeftGroup": "User left group"
},
"buttons": {
"viewChat": "Show Chat",
"viewComment": "View comment",
"viewGroup": "View group",
"viewPost": "View post"
},
"general": {
"greeting": "Hello",
"seeYou": "See you soon on ",
"yourTeam": " The {team} Team",
"settingsHint": "PS: If you don't want to receive e-mails anymore, change your ",
"settingsName": "notification settings"
},
"changedGroupMemberRole": "your role in the group “{groupName}” has been changed. Click on the button to view this group:",
"chatMessageStart": "you have received a new chat message from ",
"chatMessageEnd": ".",
"commentedOnPost": " commented on a post that you are observing with the title “{postTitle}”. Click on the button to view this comment:",
"followedUserPosted": ", a user you are following, wrote a new post with the title “{postTitle}”. Click on the button to view this post:",
"mentionedInComment": " mentioned you in a comment to the post with the title “{postTitle}”. Click on the button to view this comment:",
"mentionedInPost": " mentioned you in a post with the title “{postTitle}”. Click on the button to view this post:",
"removedUserFromGroup": "you have been removed from the group “{groupName}”.",
"postInGroup": "someone wrote a new post with the title “{postTitle}” in one of your groups. Click on the button to view this post:",
"userJoinedGroup": " joined the group “{groupName}”. Click on the button to view this group:",
"userLeftGroup": " left the group “{groupName}”. Click on the button to view this group:"
}

View File

@ -0,0 +1,87 @@
import { sendChatMessageMail } from './sendEmail'
const senderUser = {
allowEmbedIframes: false,
createdAt: '2025-04-30T00:16:49.610Z',
deleted: false,
disabled: false,
emailNotificationsChatMessage: true,
emailNotificationsCommentOnObservedPost: true,
emailNotificationsFollowingUsers: true,
emailNotificationsGroupMemberJoined: true,
emailNotificationsGroupMemberLeft: true,
emailNotificationsGroupMemberRemoved: true,
emailNotificationsGroupMemberRoleChanged: true,
emailNotificationsMention: true,
emailNotificationsPostInGroup: true,
encryptedPassword: '$2b$10$n.WujXapJrvn498lS97MD.gn8QwjWI9xlf8ckEYYtMTOPadMidcbG',
id: 'chatSender',
locale: 'en',
name: 'chatSender',
role: 'user',
showShoutsPublicly: false,
slug: 'chatsender',
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
termsAndConditionsAgreedVersion: '0.0.1',
updatedAt: '2025-04-30T00:16:49.610Z',
}
const recipientUser = {
allowEmbedIframes: false,
createdAt: '2025-04-30T00:16:49.716Z',
deleted: false,
disabled: false,
emailNotificationsChatMessage: true,
emailNotificationsCommentOnObservedPost: true,
emailNotificationsFollowingUsers: true,
emailNotificationsGroupMemberJoined: true,
emailNotificationsGroupMemberLeft: true,
emailNotificationsGroupMemberRemoved: true,
emailNotificationsGroupMemberRoleChanged: true,
emailNotificationsMention: true,
emailNotificationsPostInGroup: true,
encryptedPassword: '$2b$10$KOrCHvEB5CM7D.P3VcX2z.pSSBZKZhPqHW/QKym6V1S6fiG..xtBq',
id: 'chatReceiver',
locale: 'en',
name: 'chatReceiver',
role: 'user',
showShoutsPublicly: false,
slug: 'chatreceiver',
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
termsAndConditionsAgreedVersion: '0.0.1',
updatedAt: '2025-04-30T00:16:49.716Z',
}
describe('sendChatMessageMail', () => {
describe('English', () => {
beforeEach(() => {
recipientUser.locale = 'en'
})
it('chat_message template', async () => {
await expect(
sendChatMessageMail({
email: 'user@example.org',
senderUser,
recipientUser,
}),
).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
recipientUser.locale = 'de'
})
it('chat_message template', async () => {
await expect(
sendChatMessageMail({
email: 'user@example.org',
senderUser,
recipientUser,
}),
).resolves.toMatchSnapshot()
})
})
})

View File

@ -0,0 +1,204 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import path from 'node:path'
import Email from 'email-templates'
import { createTransport } from 'nodemailer'
// import type Email as EmailType from '@types/email-templates'
import CONFIG from '@config/index'
import logosWebapp from '@config/logos'
import metadata from '@config/metadata'
import { UserDbProperties } from '@db/types/User'
const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD
const hasDKIMData =
CONFIG.SMTP_DKIM_DOMAINNAME && CONFIG.SMTP_DKIM_KEYSELECTOR && CONFIG.SMTP_DKIM_PRIVATKEY
const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI)
const settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI)
const defaultParams = {
welcomeImageUrl,
APPLICATION_NAME: CONFIG.APPLICATION_NAME,
ORGANIZATION_NAME: metadata.ORGANIZATION_NAME,
ORGANIZATION_URL: CONFIG.ORGANIZATION_URL,
supportUrl: CONFIG.SUPPORT_URL,
settingsUrl,
}
export const transport = createTransport({
host: CONFIG.SMTP_HOST,
port: CONFIG.SMTP_PORT,
ignoreTLS: CONFIG.SMTP_IGNORE_TLS,
secure: CONFIG.SMTP_SECURE, // true for 465, false for other ports
pool: true,
maxConnections: CONFIG.SMTP_MAX_CONNECTIONS,
maxMessages: CONFIG.SMTP_MAX_MESSAGES,
auth: hasAuthData && {
user: CONFIG.SMTP_USERNAME,
pass: CONFIG.SMTP_PASSWORD,
},
dkim: hasDKIMData && {
domainName: CONFIG.SMTP_DKIM_DOMAINNAME,
keySelector: CONFIG.SMTP_DKIM_KEYSELECTOR,
privateKey: CONFIG.SMTP_DKIM_PRIVATKEY,
},
})
const email = new Email({
message: {
from: `${CONFIG.APPLICATION_NAME}`,
},
transport,
i18n: {
locales: ['en', 'de'],
defaultLocale: 'en',
retryInDefaultLocale: false,
directory: path.join(__dirname, 'locales'),
updateFiles: false,
objectNotation: true,
mustacheConfig: {
tags: ['{', '}'],
disable: false,
},
},
send: CONFIG.SEND_MAIL,
preview: false,
// This is very useful to see the emails sent by the unit tests
/*
preview: {
open: {
app: 'brave-browser',
},
},
*/
})
interface OriginalMessage {
to: string
from: string
attachments: string[]
subject: string
html: string
text: string
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const sendNotificationMail = async (notification: any): Promise<OriginalMessage> => {
const locale = notification?.to?.locale
const to = notification?.email
const name = notification?.to?.name
const template = notification?.reason
try {
const { originalMessage } = await email.send({
template: path.join(__dirname, 'templates', template),
message: {
to,
},
locals: {
...defaultParams,
locale,
name,
postTitle:
notification?.from?.__typename === 'Comment'
? notification?.from?.post?.title
: notification?.from?.title,
postUrl: new URL(
notification?.from?.__typename === 'Comment'
? `/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}`
: `/post/${notification?.from?.id}/${notification?.from?.slug}`,
CONFIG.CLIENT_URI,
),
postAuthorName:
notification?.from?.__typename === 'Comment'
? undefined
: notification?.from?.author?.name,
postAuthorUrl:
notification?.from?.__typename === 'Comment'
? undefined
: new URL(
`user/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`,
CONFIG.CLIENT_URI,
),
commenterName:
notification?.from?.__typename === 'Comment'
? notification?.from?.author?.name
: undefined,
commenterUrl:
notification?.from?.__typename === 'Comment'
? new URL(
`/user/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`,
CONFIG.CLIENT_URI,
)
: undefined,
commentUrl:
notification?.from?.__typename === 'Comment'
? new URL(
`/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}#commentId-${notification?.from?.id}`,
CONFIG.CLIENT_URI,
)
: undefined,
// chattingUser: 'SR-71',
// chatUrl: new URL('/chat', CONFIG.CLIENT_URI),
groupUrl:
notification?.from?.__typename === 'Group'
? new URL(
`/group/${notification?.from?.id}/${notification?.from?.slug}`,
CONFIG.CLIENT_URI,
)
: undefined,
groupName:
notification?.from?.__typename === 'Group' ? notification?.from?.name : undefined,
groupRelatedUserName:
notification?.from?.__typename === 'Group' ? notification?.relatedUser?.name : undefined,
groupRelatedUserUrl:
notification?.from?.__typename === 'Group'
? new URL(
`/user/${notification?.relatedUser?.id}/${notification?.relatedUser?.slug}`,
CONFIG.CLIENT_URI,
)
: undefined,
},
})
return originalMessage as OriginalMessage
} catch (error) {
throw new Error(error)
}
}
export interface ChatMessageEmailInput {
senderUser: UserDbProperties
recipientUser: UserDbProperties
email: string
}
export const sendChatMessageMail = async (
data: ChatMessageEmailInput,
): Promise<OriginalMessage> => {
const { senderUser, recipientUser } = data
const to = data.email
try {
const { originalMessage } = await email.send({
template: path.join(__dirname, 'templates', 'chat_message'),
message: {
to,
},
locals: {
...defaultParams,
locale: recipientUser.locale,
name: recipientUser.name,
chattingUser: senderUser.name,
chattingUserUrl: new URL(`/user/${senderUser.id}/${senderUser.slug}`, CONFIG.CLIENT_URI),
chatUrl: new URL('/chat', CONFIG.CLIENT_URI),
},
})
return originalMessage as OriginalMessage
} catch (error) {
throw new Error(error)
}
}

View File

@ -0,0 +1,475 @@
import { sendNotificationMail } from './sendEmail'
describe('sendNotificationMail', () => {
let locale = 'en'
describe('English', () => {
beforeEach(() => {
locale = 'en'
})
it('followed_user_posted template', async () => {
await expect(
sendNotificationMail({
reason: 'followed_user_posted',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
},
}),
).resolves.toMatchSnapshot()
})
it('post_in_group template', async () => {
await expect(
sendNotificationMail({
reason: 'post_in_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
},
}),
).resolves.toMatchSnapshot()
})
it('mentioned_in_post template', async () => {
await expect(
sendNotificationMail({
reason: 'mentioned_in_post',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
},
}),
).resolves.toMatchSnapshot()
})
it('commented_on_post template', async () => {
await expect(
sendNotificationMail({
reason: 'commented_on_post',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Comment',
id: 'c1',
slug: 'new-comment',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
post: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
},
},
}),
).resolves.toMatchSnapshot()
})
it('mentioned_in_comment template', async () => {
await expect(
sendNotificationMail({
reason: 'mentioned_in_comment',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Comment',
id: 'c1',
slug: 'new-comment',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
post: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
},
},
}),
).resolves.toMatchSnapshot()
})
it('changed_group_member_role template', async () => {
await expect(
sendNotificationMail({
reason: 'changed_group_member_role',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
}),
).resolves.toMatchSnapshot()
})
it('user_joined_group template', async () => {
await expect(
sendNotificationMail({
reason: 'user_joined_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
relatedUser: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
}),
).resolves.toMatchSnapshot()
})
it('user_left_group template', async () => {
await expect(
sendNotificationMail({
reason: 'user_left_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
relatedUser: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
}),
).resolves.toMatchSnapshot()
})
it('removed_user_from_group template', async () => {
await expect(
sendNotificationMail({
reason: 'removed_user_from_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
}),
).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
locale = 'de'
})
it('followed_user_posted template', async () => {
await expect(
sendNotificationMail({
reason: 'followed_user_posted',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
},
}),
).resolves.toMatchSnapshot()
})
it('post_in_group template', async () => {
await expect(
sendNotificationMail({
reason: 'post_in_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
},
}),
).resolves.toMatchSnapshot()
})
it('mentioned_in_post template', async () => {
await expect(
sendNotificationMail({
reason: 'mentioned_in_post',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
},
}),
).resolves.toMatchSnapshot()
})
it('commented_on_post template', async () => {
await expect(
sendNotificationMail({
reason: 'commented_on_post',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Comment',
id: 'c1',
slug: 'new-comment',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
post: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
},
},
}),
).resolves.toMatchSnapshot()
})
it('mentioned_in_comment template', async () => {
await expect(
sendNotificationMail({
reason: 'mentioned_in_comment',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Comment',
id: 'c1',
slug: 'new-comment',
author: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
post: {
id: 'p1',
slug: 'new-post',
title: 'New Post',
},
},
}),
).resolves.toMatchSnapshot()
})
it('changed_group_member_role template', async () => {
await expect(
sendNotificationMail({
reason: 'changed_group_member_role',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
}),
).resolves.toMatchSnapshot()
})
it('user_joined_group template', async () => {
await expect(
sendNotificationMail({
reason: 'user_joined_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
relatedUser: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
}),
).resolves.toMatchSnapshot()
})
it('user_left_group template', async () => {
await expect(
sendNotificationMail({
reason: 'user_left_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
relatedUser: {
id: 'u2',
name: 'Peter Lustig',
slug: 'peter-lustig',
},
}),
).resolves.toMatchSnapshot()
})
it('removed_user_from_group template', async () => {
await expect(
sendNotificationMail({
reason: 'removed_user_from_group',
email: 'user@example.org',
to: {
name: 'Jenny Rostock',
id: 'u1',
slug: 'jenny-rostock',
locale,
},
from: {
__typename: 'Group',
id: 'g1',
slug: 'the-group',
name: 'The Group',
},
}),
).resolves.toMatchSnapshot()
})
})
})

View File

@ -0,0 +1,7 @@
extend ../layout.pug
block content
.content
- var groupUrl = groupUrl
p= t('changedGroupMemberRole', { groupName })
a.button(href=groupUrl)= t('buttons.viewGroup')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.changedGroupMemberRole')}`

View File

@ -0,0 +1,8 @@
extend ../layout.pug
block content
.content
p= t('chatMessageStart')
a.user(href=chattingUserUrl)= chattingUser
= t('chatMessageEnd')
a.button(href=chatUrl)= t('buttons.viewChat')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.chatMessage')}`

View File

@ -0,0 +1,8 @@
extend ../layout.pug
block content
.content
p
a.user(href=commenterUrl)= commenterName
= t('commentedOnPost', { postTitle})
a.button(href=commentUrl)= t('buttons.viewComment')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.commentedOnPost')}`

View File

@ -0,0 +1,8 @@
extend ../layout.pug
block content
.content
p
a.user(href=postAuthorUrl)= postAuthorName
= t('followedUserPosted', { postTitle })
a.button(href=postUrl)= t('buttons.viewPost')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.followedUserPosted')}`

View File

@ -0,0 +1,5 @@
footer
.footer
- var organizationUrl = ORGANIZATION_URL
- var organizationName = ORGANIZATION_NAME
a(href=organizationUrl)= organizationName

View File

@ -0,0 +1,14 @@
//- This sets the greeting at the end of every e-mail
.text-block
- var organizationUrl = ORGANIZATION_URL
- var team = APPLICATION_NAME
- var settingsUrl = settingsUrl
p= t('general.seeYou')
a.organization(href=organizationUrl)= team
| !
p= t('general.yourTeam', { team })
br
p= t('general.settingsHint')
a.settings(href=settingsUrl)= t('general.settingsName')
| !

View File

@ -0,0 +1,9 @@
header
.head
- var img = welcomeImageUrl
img.head-logo(
alt="Welcome Image"
loading="lazy"
src=img
)

View File

@ -0,0 +1 @@
h2= `${t('general.greeting')} ${name},`

View File

@ -0,0 +1,65 @@
body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}

View File

@ -0,0 +1,26 @@
doctype html
html(lang=locale)
head
meta(
content="multipart/html; charset=UTF-8"
http-equiv="content-type"
)
meta(
name="viewport"
content="width=device-width, initial-scale=1"
)
style.
.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}
style
include includes/webflow.css
body
div.container
include includes/header.pug
include includes/salutation.pug
.wrapper
block content
include includes/greeting.pug
include includes/footer.pug

View File

@ -0,0 +1,8 @@
extend ../layout.pug
block content
.content
p
a.user(href=commenterUrl)= commenterName
= t('mentionedInComment', { postTitle})
a.button(href=commentUrl)= t('buttons.viewComment')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.mentionedInComment')}`

View File

@ -0,0 +1,8 @@
extend ../layout.pug
block content
.content
p
a.user(href=postAuthorUrl)= postAuthorName
= t('mentionedInPost', { postTitle })
a.button(href=postUrl)= t('buttons.viewPost')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.mentionedInPost')}`

View File

@ -0,0 +1,7 @@
extend ../layout.pug
block content
.content
- var postUrl = postUrl
p= t('postInGroup', { postTitle})
a.button(href=postUrl)= t('buttons.viewPost')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.postInGroup')}`

View File

@ -0,0 +1,5 @@
extend ../layout.pug
block content
.content
p= t('removedUserFromGroup', { groupName })

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.removedUserFromGroup')}`

View File

@ -0,0 +1,8 @@
extend ../layout.pug
block content
.content
p
a.user(href=groupRelatedUserUrl)= groupRelatedUserName
= t('userJoinedGroup', { groupName })
a.button(href=groupUrl)= t('buttons.viewGroup')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.userJoinedGroup')}`

View File

@ -0,0 +1,8 @@
extend ../layout.pug
block content
.content
p
a.user(href=groupRelatedUserUrl)= groupRelatedUserName
= t('userLeftGroup', { groupName })
a.button(href=groupUrl)= t('buttons.viewGroup')

View File

@ -0,0 +1 @@
= `${APPLICATION_NAME} ${t('notification')}: ${t('subjects.userLeftGroup')}`

View File

@ -2,40 +2,41 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
const driver = getDriver()
const neode = getNeode()
const database = databaseContext()
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
user: authenticatedUser,
}
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
mutate = createTestClient(server).mutate
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
beforeEach(async () => {
variables = {}
await neode.create('Category', {
await database.neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
@ -103,7 +104,7 @@ describe('CreateComment', () => {
describe('authenticated', () => {
beforeEach(async () => {
const user = await neode.create('User', { name: 'Author' })
const user = await database.neode.create('User', { name: 'Author' })
authenticatedUser = await user.toJson()
})

View File

@ -1,7 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { getNeode } from '@db/neo4j'

View File

@ -6,8 +6,8 @@
import { createTestClient } from 'apollo-server-testing'
import CONFIG from '@config/index'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import { groupMembersQuery } from '@graphql/queries/groupMembersQuery'
@ -16,10 +16,7 @@ import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation'
import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation'
import { updateGroupMutation } from '@graphql/queries/updateGroupMutation'
import createServer from '@src/server'
const driver = getDriver()
const neode = getNeode()
import createServer, { getContext } from '@src/server'
let authenticatedUser
let user
@ -35,15 +32,12 @@ const descriptionAdditional100 =
' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789'
let variables = {}
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
}
},
})
const database = databaseContext()
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
const { server } = createServer({ context })
const { mutate, query } = createTestClient(server)
const seedBasicsAndClearAuthentication = async () => {
@ -60,25 +54,25 @@ const seedBasicsAndClearAuthentication = async () => {
},
)
await Promise.all([
neode.create('Category', {
database.neode.create('Category', {
id: 'cat4',
name: 'Environment & Nature',
slug: 'environment-nature',
icon: 'tree',
}),
neode.create('Category', {
database.neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
slug: 'democracy-politics',
icon: 'university',
}),
neode.create('Category', {
database.neode.create('Category', {
id: 'cat15',
name: 'Consumption & Sustainability',
slug: 'consumption-sustainability',
icon: 'shopping-cart',
}),
neode.create('Category', {
database.neode.create('Category', {
id: 'cat27',
name: 'Animal Protection',
slug: 'animal-protection',
@ -241,7 +235,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('in mode', () => {

View File

@ -12,6 +12,7 @@ import CONFIG from '@config/index'
import { CATEGORIES_MIN, CATEGORIES_MAX } from '@constants/categories'
import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '@constants/groups'
import { removeHtmlTags } from '@middleware/helpers/cleanHtml'
import type { Context } from '@src/server'
import Resolver, {
removeUndefinedNullValuesFromObject,
@ -22,7 +23,7 @@ import { createOrUpdateLocations } from './users/location'
export default {
Query: {
Group: async (_object, params, context, _resolveInfo) => {
Group: async (_object, params, context: Context, _resolveInfo) => {
const { isMember, id, slug, first, offset } = params
let pagination = ''
const orderBy = 'ORDER BY group.createdAt DESC'
@ -75,10 +76,10 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
GroupMembers: async (_object, params, context, _resolveInfo) => {
GroupMembers: async (_object, params, context: Context, _resolveInfo) => {
const { id: groupId } = params
const session = context.driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
@ -96,7 +97,7 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
GroupCount: async (_object, params, context, _resolveInfo) => {
@ -134,7 +135,7 @@ export default {
},
},
Mutation: {
CreateGroup: async (_parent, params, context, _resolveInfo) => {
CreateGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { categoryIds } = params
delete params.categoryIds
params.locationName = params.locationName === '' ? null : params.locationName
@ -182,7 +183,7 @@ export default {
`,
{ userId: context.user.id, categoryIds, params },
)
const [group] = await ownerCreateGroupTransactionResponse.records.map((record) =>
const [group] = ownerCreateGroupTransactionResponse.records.map((record) =>
record.get('group'),
)
return group
@ -197,10 +198,10 @@ export default {
throw new UserInputError('Group with this slug already exists!')
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
UpdateGroup: async (_parent, params, context, _resolveInfo) => {
UpdateGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { categoryIds } = params
delete params.categoryIds
const { id: groupId, avatar: avatarInput } = params
@ -257,7 +258,7 @@ export default {
categoryIds,
params,
})
const [group] = await transactionResponse.records.map((record) => record.get('group'))
const [group] = transactionResponse.records.map((record) => record.get('group'))
if (avatarInput) {
await mergeImage(group, 'AVATAR_IMAGE', avatarInput, { transaction })
}
@ -273,10 +274,10 @@ export default {
throw new UserInputError('Group with this slug already exists!')
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
JoinGroup: async (_parent, params, context, _resolveInfo) => {
JoinGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId, userId } = params
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
@ -294,7 +295,7 @@ export default {
RETURN member {.*, myRoleInGroup: membership.role}
`
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
const [member] = await transactionResponse.records.map((record) => record.get('member'))
const [member] = transactionResponse.records.map((record) => record.get('member'))
return member
})
try {
@ -302,10 +303,10 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
LeaveGroup: async (_parent, params, context, _resolveInfo) => {
LeaveGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId, userId } = params
const session = context.driver.session()
try {
@ -313,10 +314,10 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => {
ChangeGroupMemberRole: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId, userId, roleInGroup } = params
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
@ -353,7 +354,7 @@ export default {
userId,
roleInGroup,
})
const [member] = await transactionResponse.records.map((record) => record.get('member'))
const [member] = transactionResponse.records.map((record) => record.get('member'))
return member
})
try {
@ -361,10 +362,10 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
RemoveUserFromGroup: async (_parent, params, context, _resolveInfo) => {
RemoveUserFromGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId, userId } = params
const session = context.driver.session()
try {
@ -372,10 +373,10 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
muteGroup: async (_parent, params, context, _resolveInfo) => {
muteGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId } = params
const userId = context.user.id
const session = context.driver.session()
@ -393,7 +394,7 @@ export default {
userId,
},
)
const [group] = await transactionResponse.records.map((record) => record.get('group'))
const [group] = transactionResponse.records.map((record) => record.get('group'))
return group
})
try {
@ -401,10 +402,10 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
unmuteGroup: async (_parent, params, context, _resolveInfo) => {
unmuteGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId } = params
const userId = context.user.id
const session = context.driver.session()
@ -422,7 +423,7 @@ export default {
userId,
},
)
const [group] = await transactionResponse.records.map((record) => record.get('group'))
const [group] = transactionResponse.records.map((record) => record.get('group'))
return group
})
try {
@ -430,7 +431,7 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
},

Some files were not shown because too many files have changed in this diff Show More