Merge branch 'master' into chat-message-notification-e2e-tests
9
.github/dependabot.yml
vendored
@ -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
|
||||
|
||||
141
.github/workflows/test-e2e.yml
vendored
@ -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 }}
|
||||
2
.github/workflows/test.lint_pr.yml
vendored
@ -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:
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
coverage:
|
||||
range: "60...100"
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"schemaPath": "./src/schema.graphql"
|
||||
}
|
||||
@ -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"]
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "10"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-throw-expressions"
|
||||
]
|
||||
}
|
||||
@ -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 = {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 |
@ -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 |
@ -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"></></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"></></tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.6 KiB |
@ -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 |
@ -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 = {
|
||||
|
||||
3
backend/src/constants/subscriptions.ts
Normal 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'
|
||||
49
backend/src/context/database.ts
Normal 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),
|
||||
}
|
||||
}
|
||||
25
backend/src/context/pubsub.ts
Normal 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),
|
||||
})
|
||||
}
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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 () {
|
||||
|
||||
@ -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',
|
||||
}),
|
||||
}
|
||||
|
||||
32
backend/src/db/types/User.ts
Normal 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>
|
||||
@ -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",
|
||||
}
|
||||
`;
|
||||
2209
backend/src/emails/__snapshots__/sendNotificationMail.spec.ts.snap
Normal file
39
backend/src/emails/locales/de.json
Normal 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:"
|
||||
}
|
||||
39
backend/src/emails/locales/en.json
Normal 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:"
|
||||
}
|
||||
87
backend/src/emails/sendChatMessageMail.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
204
backend/src/emails/sendEmail.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
475
backend/src/emails/sendNotificationMail.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,7 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
- var groupUrl = groupUrl
|
||||
p= t('changedGroupMemberRole', { groupName })
|
||||
a.button(href=groupUrl)= t('buttons.viewGroup')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.changedGroupMemberRole')}`
|
||||
8
backend/src/emails/templates/chat_message/html.pug
Normal 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')
|
||||
1
backend/src/emails/templates/chat_message/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.chatMessage')}`
|
||||
8
backend/src/emails/templates/commented_on_post/html.pug
Normal 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')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.commentedOnPost')}`
|
||||
@ -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')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.followedUserPosted')}`
|
||||
5
backend/src/emails/templates/includes/footer.pug
Normal file
@ -0,0 +1,5 @@
|
||||
footer
|
||||
.footer
|
||||
- var organizationUrl = ORGANIZATION_URL
|
||||
- var organizationName = ORGANIZATION_NAME
|
||||
a(href=organizationUrl)= organizationName
|
||||
14
backend/src/emails/templates/includes/greeting.pug
Normal 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')
|
||||
| !
|
||||
|
||||
9
backend/src/emails/templates/includes/header.pug
Normal file
@ -0,0 +1,9 @@
|
||||
header
|
||||
.head
|
||||
- var img = welcomeImageUrl
|
||||
img.head-logo(
|
||||
alt="Welcome Image"
|
||||
loading="lazy"
|
||||
src=img
|
||||
)
|
||||
|
||||
1
backend/src/emails/templates/includes/salutation.pug
Normal file
@ -0,0 +1 @@
|
||||
h2= `${t('general.greeting')} ${name},`
|
||||
65
backend/src/emails/templates/includes/webflow.css
Normal 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;
|
||||
}
|
||||
26
backend/src/emails/templates/layout.pug
Normal 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
|
||||
@ -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')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.mentionedInComment')}`
|
||||
8
backend/src/emails/templates/mentioned_in_post/html.pug
Normal 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')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.mentionedInPost')}`
|
||||
7
backend/src/emails/templates/post_in_group/html.pug
Normal 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')
|
||||
1
backend/src/emails/templates/post_in_group/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.postInGroup')}`
|
||||
@ -0,0 +1,5 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p= t('removedUserFromGroup', { groupName })
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.removedUserFromGroup')}`
|
||||
8
backend/src/emails/templates/user_joined_group/html.pug
Normal 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')
|
||||
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.userJoinedGroup')}`
|
||||
8
backend/src/emails/templates/user_left_group/html.pug
Normal 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')
|
||||
1
backend/src/emails/templates/user_left_group/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${APPLICATION_NAME} – ${t('notification')}: ${t('subjects.userLeftGroup')}`
|
||||
@ -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()
|
||||
})
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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', () => {
|
||||
@ -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()
|
||||
}
|
||||
},
|
||||
},
|
||||