Merge branch 'master' into brand-reformer-network-first-step

This commit is contained in:
sebastian2357 2025-05-23 16:40:00 +02:00 committed by GitHub
commit 46ec1c4344
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 2626 additions and 640 deletions

View File

@ -81,7 +81,7 @@ jobs:
type=sha
- name: Build and push Docker images
id: push
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0
with:
context: ${{ matrix.app.context }}
target: ${{ matrix.app.target }}

View File

@ -64,7 +64,7 @@ jobs:
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
#- name: Repository Dispatch
# uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0
# uses: peter-evans/repository-dispatch@63fb3226c1bffa7d9e09d27eef4ecb0c3cf51143 # v3.0.0
# with:
# token: ${{ github.token }}
# event-type: trigger-ocelot-build-success
@ -72,7 +72,7 @@ jobs:
# client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
- name: Repository Dispatch stage.ocelot.social
uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0
uses: peter-evans/repository-dispatch@63fb3226c1bffa7d9e09d27eef4ecb0c3cf51143 # v3.0.0
with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success
@ -80,7 +80,7 @@ jobs:
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
- name: Repository Dispatch stage.yunite.me
uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0
uses: peter-evans/repository-dispatch@63fb3226c1bffa7d9e09d27eef4ecb0c3cf51143 # v3.0.0
with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success

View File

@ -4,8 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [3.6.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.6.0...3.6.1)
- fix(webapp): fix flickering? [`#8549`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8549)
- fix(backend): fix statistics and introduce new values [`#8550`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8550)
- Fix typo in german translation [`#8548`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8548)
- feat(webapp): default language configurable [`#8546`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8546)
- fix(webapp): query categories on login to get the count [`#8542`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8542)
#### [3.6.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.5.3...3.6.0)
> 10 May 2025
- v3.6.0 [`#8541`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8541)
- Show invititation dropdown until user clicks somewhere else [`#8539`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8539)
- feat(webapp): redirect to group after registration with invite to group [`#8540`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8540)
- fix(webapp): fix layout break and hidden group name appearance [`#8538`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8538)

View File

@ -7,7 +7,7 @@ GRAPHQL_URI=http://localhost:4000
CLIENT_URI=http://localhost:3000
# E-Mail default settings
EMAIL_SUPPORT="devops@ocelot.social"
SUPPORT_EMAIL="devops@ocelot.social"
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
SMTP_HOST=
SMTP_PORT=

View File

@ -7,7 +7,7 @@ GRAPHQL_URI=http://localhost:4000
CLIENT_URI=http://localhost:3000
# E-Mail default settings
EMAIL_SUPPORT="devops@ocelot.social"
SUPPORT_EMAIL="devops@ocelot.social"
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
SMTP_HOST=mailserver
SMTP_PORT=1025

View File

@ -1,4 +1,4 @@
FROM node:23.11.0-alpine AS base
FROM node:24.0.2-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"

View File

@ -1,6 +1,6 @@
{
"name": "ocelot-social-backend",
"version": "3.6.0",
"version": "3.6.1",
"description": "GraphQL Backend for ocelot.social",
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
"author": "ocelot.social Community",
@ -28,10 +28,12 @@
"prod:db:data:categories": "node build/src/db/categories.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.796.0",
"@aws-sdk/lib-storage": "^3.797.0",
"@sentry/node": "^5.15.4",
"@types/mime-types": "^2.1.4",
"apollo-server": "~2.14.2",
"apollo-server-express": "^2.14.2",
"aws-sdk": "^2.1692.0",
"bcryptjs": "~3.0.2",
"body-parser": "^1.20.3",
"cheerio": "~1.0.0",
@ -51,7 +53,7 @@
"ioredis": "^5.6.1",
"jsonwebtoken": "~8.5.1",
"languagedetect": "^2.0.0",
"linkify-html": "^4.2.0",
"linkify-html": "^4.3.1",
"linkifyjs": "^4.2.0",
"lodash": "~4.17.21",
"merge-graphql-schemas": "^1.7.8",
@ -82,7 +84,7 @@
"preview-email": "^3.1.0",
"pug": "^3.0.3",
"request": "~2.88.2",
"sanitize-html": "~2.16.0",
"sanitize-html": "~2.17.0",
"slug": "~9.1.0",
"trunc-html": "~1.1.2",
"uuid": "~9.0.1",
@ -91,18 +93,18 @@
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
"@faker-js/faker": "9.7.0",
"@faker-js/faker": "9.8.0",
"@types/email-templates": "^10.0.4",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.16",
"@types/node": "^22.15.3",
"@types/node": "^22.15.18",
"@types/slug": "^5.0.9",
"@types/uuid": "~9.0.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"apollo-server-testing": "~2.11.0",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.2",
"eslint-config-prettier": "^10.1.5",
"eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^4.3.4",
"eslint-plugin-import": "^2.31.0",
@ -110,7 +112,7 @@
"eslint-plugin-jsonc": "^2.20.0",
"eslint-plugin-n": "^17.17.0",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-security": "^3.0.1",
"jest": "^29.7.0",
@ -118,9 +120,9 @@
"prettier": "^3.5.3",
"require-json5": "^1.3.0",
"rosie": "^2.1.1",
"ts-jest": "^29.3.2",
"ts-jest": "^29.3.4",
"ts-node": "^10.9.2",
"tsc-alias": "^1.8.15",
"tsc-alias": "^1.8.16",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3"
},

View File

@ -1,8 +1,5 @@
// this file is duplicated in `backend/src/config/` and `webapp/constants/` and replaced on rebranding by https://github.com/Ocelot-Social-Community/Ocelot-Social-Deploy-Rebranding/tree/master/branding/constants/
export default {
SUPPORT_EMAIL: 'support@reformer.network',
MODERATION_EMAIL: 'support@reformer.network',
// ATTENTION: the following links have to be defined even for internal pages with full URLs as example like 'https://staging.ocelot.social/support', because they are used in e-mails!
ORGANIZATION_LINK: 'https://reformer.network/organization',
SUPPORT_LINK: 'https://reformer.network/support',
}

View File

@ -112,6 +112,7 @@ const s3 = {
const options = {
EMAIL_DEFAULT_SENDER: env.EMAIL_DEFAULT_SENDER,
SUPPORT_EMAIL: env.SUPPORT_EMAIL,
SUPPORT_URL: emails.SUPPORT_LINK,
APPLICATION_NAME: metadata.APPLICATION_NAME,
ORGANIZATION_URL: emails.ORGANIZATION_LINK,
@ -124,6 +125,10 @@ const options = {
CATEGORIES_ACTIVE: process.env.CATEGORIES_ACTIVE === 'true' || false,
}
const language = {
LANGUAGE_DEFAULT: process.env.LANGUAGE_DEFAULT ?? 'en',
}
// Check if all required configs are present
Object.entries(required).map((entry) => {
if (!entry[1]) {
@ -141,6 +146,7 @@ export default {
...redis,
...s3,
...options,
...language,
}
export { nodemailerTransportOptions }

View File

@ -99,6 +99,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -121,6 +124,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -226,6 +232,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -248,6 +257,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",

View File

@ -99,6 +99,9 @@ footer {
<p> The ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -124,6 +127,9 @@ See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -229,6 +235,9 @@ footer {
<p> Dein ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -254,6 +263,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",

View File

@ -98,6 +98,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -120,6 +123,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -225,6 +231,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -248,6 +257,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -353,6 +365,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -376,6 +391,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -481,6 +499,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -504,6 +525,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -609,6 +633,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -631,6 +658,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -735,6 +765,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -757,6 +790,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -861,6 +897,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -880,6 +919,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -985,6 +1027,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1007,6 +1052,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -1112,6 +1160,9 @@ footer {
<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>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1134,6 +1185,9 @@ See you soon on ocelot.social [https://ocelot.social]!
PS: If you don't want to receive e-mails anymore, change your notification
settings [http://webapp:3000/settings/notifications]!
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -1238,6 +1292,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1260,6 +1317,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -1365,6 +1425,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1388,6 +1451,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -1493,6 +1559,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1516,6 +1585,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -1621,6 +1693,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1644,6 +1719,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -1749,6 +1827,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1771,6 +1852,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -1875,6 +1959,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -1897,6 +1984,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -2001,6 +2091,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -2020,6 +2113,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -2125,6 +2221,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -2147,6 +2246,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -2252,6 +2354,9 @@ footer {
<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>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -2274,6 +2379,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine
Benachrichtigungseinstellungen [http://webapp:3000/settings/notifications]!
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",

View File

@ -102,6 +102,9 @@ footer {
<p> The ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -133,6 +136,9 @@ See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -241,6 +247,9 @@ footer {
<p> Dein ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -273,6 +282,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -381,6 +393,9 @@ footer {
<p> The ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -412,6 +427,9 @@ See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -520,6 +538,9 @@ footer {
<p> Dein ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -552,6 +573,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",

View File

@ -99,6 +99,9 @@ footer {
<p> The ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -123,6 +126,9 @@ See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -228,6 +234,9 @@ footer {
<p> Dein ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -253,6 +262,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",

View File

@ -99,6 +99,9 @@ footer {
<p> The ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -121,6 +124,9 @@ See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
If you have questions or problems, feel free to contact our support:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
@ -226,6 +232,9 @@ footer {
<p> Dein ocelot.social Team</p>
</div>
</div>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
@ -248,6 +257,9 @@ Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",

View File

@ -0,0 +1,531 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendResetPasswordMail with support English renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social <devops@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;
}
span {
color: #17b53e;
}
.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 Jenny Rostock,</h2>
<div class="wrapper">
<div class="content"></div>
<p>So, you forgot your password? No problem! Just click the button below to reset it within the next 24 hours:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&amp;nonce=123456">Confirm your e-mail address</a>
<p>If you didn't request a new password feel free to ignore this e-mail.</p>
<p>If the above button doesn't work you can also copy the following code into your browser window: <span>123456</span></p>
<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>
</div>
</div>
<div class="support">
<p>If you have questions or problems, feel free to contact our support: <a href="mailto:support@example.org">support@example.org</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Reset Password ocelot.social",
"text": "HELLO JENNY ROSTOCK,
So, you forgot your password? No problem! Just click the button below to reset
it within the next 24 hours:
Confirm your e-mail address
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
If you didn't request a new password feel free to ignore this e-mail.
If the above button doesn't work you can also copy the following code into your
browser window: 123456
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
If you have questions or problems, feel free to contact our support:
support@example.org [support@example.org]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendResetPasswordMail with support German renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social <devops@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;
}
span {
color: #17b53e;
}
.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 Jenny Rostock,</h2>
<div class="wrapper">
<div class="content"></div>
<p>Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&amp;nonce=123456">Bestätige deine E-Mail Adresse</a>
<p>Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.</p>
<p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in dein Browserfenster kopieren: <span>123456</span></p>
<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>
</div>
</div>
<div class="support">
<p>Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: <a href="mailto:support@example.org">support@example.org</a></p>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Neues Passwort ocelot.social",
"text": "HALLO JENNY ROSTOCK,
Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button
kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:
Bestätige deine E-Mail Adresse
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach
ignorieren.
Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in
dein Browserfenster kopieren: 123456
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden:
support@example.org [support@example.org]
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendResetPasswordMail without support English renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social <devops@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;
}
span {
color: #17b53e;
}
.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 Jenny Rostock,</h2>
<div class="wrapper">
<div class="content"></div>
<p>So, you forgot your password? No problem! Just click the button below to reset it within the next 24 hours:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&amp;nonce=123456">Confirm your e-mail address</a>
<p>If you didn't request a new password feel free to ignore this e-mail.</p>
<p>If the above button doesn't work you can also copy the following code into your browser window: <span>123456</span></p>
<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>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Reset Password ocelot.social",
"text": "HELLO JENNY ROSTOCK,
So, you forgot your password? No problem! Just click the button below to reset
it within the next 24 hours:
Confirm your e-mail address
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
If you didn't request a new password feel free to ignore this e-mail.
If the above button doesn't work you can also copy the following code into your
browser window: 123456
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendResetPasswordMail without support German renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social <devops@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;
}
span {
color: #17b53e;
}
.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 Jenny Rostock,</h2>
<div class="wrapper">
<div class="content"></div>
<p>Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&amp;nonce=123456">Bestätige deine E-Mail Adresse</a>
<p>Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.</p>
<p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in dein Browserfenster kopieren: <span>123456</span></p>
<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>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Neues Passwort ocelot.social",
"text": "HALLO JENNY ROSTOCK,
Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button
kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:
Bestätige deine E-Mail Adresse
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach
ignorieren.
Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in
dein Browserfenster kopieren: 123456
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;

View File

@ -28,6 +28,7 @@
"introduction": "Du möchtest also deine E-Mail ändern? Kein Problem! Mit Klick auf diesen Button kannst du deine neue E-Mail Adresse bestätigen:",
"doNotChange": "Falls du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht einfach ignorieren. "
},
"support": "Wenn du Fragen oder Probleme hast, kannst du dich gerne an den Support wenden: ",
"buttons": {
"confirmEmail": "Bestätige deine E-Mail Adresse",
"resetPassword": "Passwort zurücksetzen",

View File

@ -28,6 +28,7 @@
"introduction": "So, you want to change your e-mail? No problem! Just click the button below to verify your new address:",
"doNotChange": "If you don't want to change your e-mail address feel free to ignore this message. "
},
"support": "If you have questions or problems, feel free to contact our support: ",
"buttons": {
"confirmEmail": "Confirm your e-mail address",
"resetPassword": "Reset password",

View File

@ -18,11 +18,12 @@ import { UserDbProperties } from '@db/types/User'
const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI)
const settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI)
const defaultParams = {
export const defaultParams = {
welcomeImageUrl,
APPLICATION_NAME: CONFIG.APPLICATION_NAME,
ORGANIZATION_NAME: metadata.ORGANIZATION_NAME,
ORGANIZATION_URL: CONFIG.ORGANIZATION_URL,
SUPPORT_EMAIL: CONFIG.SUPPORT_EMAIL,
supportUrl: CONFIG.SUPPORT_URL,
settingsUrl,
renderSettingsUrl: true,
@ -39,7 +40,7 @@ const email = new Email({
transport,
i18n: {
locales: ['en', 'de'],
defaultLocale: 'en',
defaultLocale: CONFIG.LANGUAGE_DEFAULT,
retryInDefaultLocale: false,
directory: path.join(__dirname, 'locales'),
updateFiles: false,

View File

@ -0,0 +1,67 @@
import { sendResetPasswordMail, defaultParams } from './sendEmail'
describe('sendResetPasswordMail', () => {
const data: {
email: string
nonce: string
locale: string
name: string
} = {
email: 'user@example.org',
nonce: '123456',
locale: 'en',
name: 'Jenny Rostock',
}
describe('with support', () => {
beforeEach(() => {
defaultParams.SUPPORT_EMAIL = 'support@example.org'
})
describe('English', () => {
beforeEach(() => {
data.locale = 'en'
})
it('renders correctly', async () => {
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
data.locale = 'de'
})
it('renders correctly', async () => {
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
})
})
})
describe('without support', () => {
beforeEach(() => {
delete defaultParams.SUPPORT_EMAIL
})
describe('English', () => {
beforeEach(() => {
data.locale = 'en'
})
it('renders correctly', async () => {
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
data.locale = 'de'
})
it('renders correctly', async () => {
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
})
})
})
})

View File

@ -0,0 +1,2 @@
p= t('support')
a(href='mailto:' + supportEmail)= supportEmail

View File

@ -27,4 +27,9 @@ html(lang=locale)
block content
include includes/greeting.pug
- var supportEmail = SUPPORT_EMAIL
if supportEmail
.support
include includes/support.pug
include includes/footer.pug

View File

@ -0,0 +1,29 @@
import gql from 'graphql-tag'
export const statistics = gql`
query statistics {
statistics {
users
usersDeleted
posts
comments
notifications
emails
follows
shouts
invites
chatMessages
chatRooms
tags
locations
groups
inviteCodes
inviteCodesExpired
inviteCodesRedeemed
badgesRewarded
badgesDisplayed
usersVerified
reports
}
}
`

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/await-thenable */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-call */
@ -13,6 +13,9 @@ import { getNeode, getDriver } from '@db/neo4j'
import { deleteImage, mergeImage } from './images'
import type { ImageInput } from './images'
import type { FileUpload } from 'graphql-upload'
const driver = getDriver()
const neode = getNeode()
const uuid = '[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}'
@ -55,7 +58,7 @@ describe('deleteImage', () => {
user = await user.toJson()
})
it('soft deletes `Image` node', async () => {
it('deletes `Image` node', async () => {
await expect(neode.all('Image')).resolves.toHaveLength(1)
await deleteImage(user, 'AVATAR_IMAGE', { deleteCallback })
await expect(neode.all('Image')).resolves.toHaveLength(0)
@ -71,7 +74,7 @@ describe('deleteImage', () => {
describe('given a transaction parameter', () => {
it('executes cypher statements within the transaction', async () => {
const session = driver.session()
let someString
let someString: string
try {
someString = await session.writeTransaction(async (transaction) => {
await deleteImage(user, 'AVATAR_IMAGE', {
@ -86,7 +89,7 @@ describe('deleteImage', () => {
await session.close()
}
await expect(neode.all('Image')).resolves.toHaveLength(0)
await expect(someString).toEqual('Hello')
expect(someString).toEqual('Hello')
})
it('rolls back the transaction in case of errors', async () => {
@ -114,7 +117,7 @@ describe('deleteImage', () => {
})
describe('mergeImage', () => {
let imageInput
let imageInput: ImageInput
let post
beforeEach(() => {
imageInput = {
@ -124,18 +127,19 @@ describe('mergeImage', () => {
describe('given image.upload', () => {
beforeEach(() => {
const createReadStream: FileUpload['createReadStream'] = (() => ({
pipe: () => ({
on: (_, callback) => callback(),
}),
})) as unknown as FileUpload['createReadStream']
imageInput = {
...imageInput,
upload: {
upload: Promise.resolve({
filename: 'image.jpg',
mimetype: 'image/jpeg',
encoding: '7bit',
createReadStream: () => ({
pipe: () => ({
on: (_, callback) => callback(),
}),
}),
},
createReadStream,
}),
}
})
@ -173,7 +177,12 @@ describe('mergeImage', () => {
})
it('creates a url safe name', async () => {
imageInput.upload.filename = '/path/to/awkward?/ file-location/?foo- bar-avatar.jpg'
if (!imageInput.upload) {
throw new Error('Test imageInput was not setup correctly.')
}
const upload = await imageInput.upload
upload.filename = '/path/to/awkward?/ file-location/?foo- bar-avatar.jpg'
imageInput.upload = Promise.resolve(upload)
await expect(
mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }),
).resolves.toMatchObject({
@ -181,21 +190,6 @@ describe('mergeImage', () => {
})
})
// eslint-disable-next-line jest/no-disabled-tests
it.skip('automatically creates different image sizes', async () => {
await expect(
mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }),
).resolves.toEqual({
url: expect.any(String),
alt: expect.any(String),
urlW34: expect.stringMatching(new RegExp(`^/uploads/W34/${uuid}-image.jpg`)),
urlW160: expect.stringMatching(new RegExp(`^/uploads/W160/${uuid}-image.jpg`)),
urlW320: expect.stringMatching(new RegExp(`^/uploads/W320/${uuid}-image.jpg`)),
urlW640: expect.stringMatching(new RegExp(`^/uploads/W640/${uuid}-image.jpg`)),
urlW1024: expect.stringMatching(new RegExp(`^/uploads/W1024/${uuid}-image.jpg`)),
})
})
it('connects resource with image via given image type', async () => {
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
const result = await neode.cypher(

View File

@ -7,22 +7,57 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable promise/avoid-new */
/* eslint-disable security/detect-non-literal-fs-filename */
import { existsSync, unlinkSync, createWriteStream } from 'node:fs'
import path from 'node:path'
import { S3Client, DeleteObjectCommand, ObjectCannedACL } from '@aws-sdk/client-s3'
import { Upload } from '@aws-sdk/lib-storage'
import { UserInputError } from 'apollo-server'
import { S3 } from 'aws-sdk'
import slug from 'slug'
import { v4 as uuid } from 'uuid'
import CONFIG from '@config/index'
import { getDriver } from '@db/neo4j'
// const widths = [34, 160, 320, 640, 1024]
const { AWS_ENDPOINT: endpoint, AWS_REGION: region, AWS_BUCKET: Bucket, S3_CONFIGURED } = CONFIG
import type { FileUpload } from 'graphql-upload'
import type { Transaction } from 'neo4j-driver'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function deleteImage(resource, relationshipType, opts: any = {}) {
type FileDeleteCallback = (url: string) => Promise<void>
type FileUploadCallback = (
upload: Pick<FileUpload, 'createReadStream' | 'mimetype'> & { uniqueFilename: string },
) => Promise<string>
export interface ImageInput {
upload?: Promise<FileUpload>
alt?: string
sensitive?: boolean
aspectRatio?: number
type?: string
}
// const widths = [34, 160, 320, 640, 1024]
const { AWS_BUCKET: Bucket, S3_CONFIGURED } = CONFIG
const createS3Client = () => {
const { AWS_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = CONFIG
if (!(AWS_ENDPOINT && AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY)) {
throw new Error('Missing AWS credentials.')
}
return new S3Client({
credentials: {
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
},
endpoint: AWS_ENDPOINT,
forcePathStyle: true,
})
}
interface DeleteImageOpts {
transaction?: Transaction
deleteCallback?: FileDeleteCallback
}
export async function deleteImage(resource, relationshipType, opts: DeleteImageOpts = {}) {
sanitizeRelationshipType(relationshipType)
const { transaction, deleteCallback } = opts
if (!transaction) return wrapTransaction(deleteImage, [resource, relationshipType], opts)
@ -44,8 +79,18 @@ export async function deleteImage(resource, relationshipType, opts: any = {}) {
return image
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function mergeImage(resource, relationshipType, imageInput, opts: any = {}) {
interface MergeImageOpts {
transaction?: Transaction
uploadCallback?: FileUploadCallback
deleteCallback?: FileDeleteCallback
}
export async function mergeImage(
resource,
relationshipType,
imageInput: ImageInput | null | undefined,
opts: MergeImageOpts = {},
) {
if (typeof imageInput === 'undefined') return
if (imageInput === null) return deleteImage(resource, relationshipType, opts)
sanitizeRelationshipType(relationshipType)
@ -95,20 +140,25 @@ const wrapTransaction = async (wrappedCallback, args, opts) => {
}
}
const deleteImageFile = (image, deleteCallback) => {
const deleteImageFile = (image, deleteCallback: FileDeleteCallback | undefined) => {
if (!deleteCallback) {
deleteCallback = S3_CONFIGURED ? s3Delete : localFileDelete
}
const { url } = image
// eslint-disable-next-line @typescript-eslint/no-floating-promises
deleteCallback(url)
return url
}
const uploadImageFile = async (upload, uploadCallback) => {
const uploadImageFile = async (
upload: Promise<FileUpload> | undefined,
uploadCallback: FileUploadCallback | undefined,
) => {
if (!upload) return undefined
if (!uploadCallback) {
uploadCallback = S3_CONFIGURED ? s3Upload : localFileUpload
}
// eslint-disable-next-line @typescript-eslint/unbound-method
const { createReadStream, filename, mimetype } = await upload
const { name, ext } = path.parse(filename)
const uniqueFilename = `${uuid()}-${slug(name)}${ext}`
@ -123,7 +173,7 @@ const sanitizeRelationshipType = (relationshipType) => {
}
}
const localFileUpload = ({ createReadStream, uniqueFilename }) => {
const localFileUpload: FileUploadCallback = ({ createReadStream, uniqueFilename }) => {
const destination = `/uploads/${uniqueFilename}`
return new Promise((resolve, reject) =>
createReadStream().pipe(
@ -134,41 +184,42 @@ const localFileUpload = ({ createReadStream, uniqueFilename }) => {
)
}
const s3Upload = async ({ createReadStream, uniqueFilename, mimetype }) => {
const s3 = new S3({ region, endpoint })
const s3Upload: FileUploadCallback = async ({ createReadStream, uniqueFilename, mimetype }) => {
const s3Location = `original/${uniqueFilename}`
if (!Bucket) {
throw new Error('AWS_BUCKET is undefined')
}
const params = {
Bucket,
Key: s3Location,
ACL: 'public-read',
ACL: ObjectCannedACL.public_read,
ContentType: mimetype,
Body: createReadStream(),
}
const data = await s3.upload(params).promise()
const s3 = createS3Client()
const command = new Upload({ client: s3, params })
const data = await command.done()
const { Location } = data
if (!Location) {
throw new Error('File upload did not return `Location`')
}
return Location
}
const localFileDelete = async (url) => {
const localFileDelete: FileDeleteCallback = async (url) => {
const location = `public${url}`
// eslint-disable-next-line n/no-sync
if (existsSync(location)) unlinkSync(location)
}
const s3Delete = async (url) => {
const s3 = new S3({ region, endpoint })
const s3Delete: FileDeleteCallback = async (url) => {
let { pathname } = new URL(url, 'http://example.org') // dummy domain to avoid invalid URL error
pathname = pathname.substring(1) // remove first character '/'
if (!Bucket) {
throw new Error('AWS_BUCKET is undefined')
const prefix = `${Bucket}/`
if (pathname.startsWith(prefix)) {
pathname = pathname.slice(prefix.length)
}
const params = {
Bucket,
Key: pathname,
}
await s3.deleteObject(params).promise()
const s3 = createS3Client()
await s3.send(new DeleteObjectCommand(params))
}

View File

@ -54,7 +54,7 @@ export default {
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
${whereClause}
OPTIONAL MATCH (relatedUser:User { id: notification.relatedUserId })
OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(relatedUser)
OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(user)
WITH user, notification, resource, membership, relatedUser,
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author), postType: [l IN labels(post) WHERE NOT l = 'Post']} ] AS posts

View File

@ -2,52 +2,39 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* 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 { statistics } from '@graphql/queries/statistics'
import createServer, { getContext } from '@src/server'
const database = databaseContext()
let server: ApolloServer
let query, authenticatedUser
const instance = getNeode()
const driver = getDriver()
const statisticsQuery = gql`
query {
statistics {
countUsers
countPosts
countComments
countNotifications
countInvites
countFollows
countShouts
}
}
`
beforeAll(async () => {
await cleanDatabase()
authenticatedUser = undefined
const { server } = createServer({
context: () => {
return {
driver,
neode: instance,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
// eslint-disable-next-line @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
afterEach(async () => {
await cleanDatabase()
})
@ -63,8 +50,8 @@ describe('statistics', () => {
})
it('returns the count of all users', async () => {
await expect(query({ query: statisticsQuery })).resolves.toMatchObject({
data: { statistics: { countUsers: 6 } },
await expect(query({ query: statistics })).resolves.toMatchObject({
data: { statistics: { users: 6 } },
errors: undefined,
})
})
@ -80,8 +67,8 @@ describe('statistics', () => {
})
it('returns the count of all posts', async () => {
await expect(query({ query: statisticsQuery })).resolves.toMatchObject({
data: { statistics: { countPosts: 3 } },
await expect(query({ query: statistics })).resolves.toMatchObject({
data: { statistics: { posts: 3 } },
errors: undefined,
})
})
@ -97,8 +84,8 @@ describe('statistics', () => {
})
it('returns the count of all comments', async () => {
await expect(query({ query: statisticsQuery })).resolves.toMatchObject({
data: { statistics: { countComments: 2 } },
await expect(query({ query: statistics })).resolves.toMatchObject({
data: { statistics: { comments: 2 } },
errors: undefined,
})
})
@ -116,8 +103,8 @@ describe('statistics', () => {
})
it('returns the count of all follows', async () => {
await expect(query({ query: statisticsQuery })).resolves.toMatchObject({
data: { statistics: { countFollows: 1 } },
await expect(query({ query: statistics })).resolves.toMatchObject({
data: { statistics: { follows: 1 } },
errors: undefined,
})
})
@ -143,8 +130,8 @@ describe('statistics', () => {
})
it('returns the count of all shouts', async () => {
await expect(query({ query: statisticsQuery })).resolves.toMatchObject({
data: { statistics: { countShouts: 2 } },
await expect(query({ query: statistics })).resolves.toMatchObject({
data: { statistics: { shouts: 2 } },
errors: undefined,
})
})

View File

@ -1,48 +1,83 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable security/detect-object-injection */
/* eslint-disable @typescript-eslint/dot-notation */
import { Context } from '@src/server'
export default {
Query: {
statistics: async (_parent, _args, { driver }) => {
const session = driver.session()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const counts: any = {}
try {
const mapping = {
countUsers: 'User',
countPosts: 'Post',
countComments: 'Comment',
countNotifications: 'NOTIFIED',
countEmails: 'EmailAddress',
countFollows: 'FOLLOWS',
countShouts: 'SHOUTED',
}
const statisticsReadTxResultPromise = session.readTransaction(async (transaction) => {
const statisticsTransactionResponse = await transaction.run(
`
CALL apoc.meta.stats() YIELD labels, relTypesCount
RETURN labels, relTypesCount
`,
)
return statisticsTransactionResponse.records.map((record) => {
return {
...record.get('labels'),
...record.get('relTypesCount'),
}
})
})
const [statistics] = await statisticsReadTxResultPromise
Object.keys(mapping).forEach((key) => {
const stat = statistics[mapping[key]]
counts[key] = stat ? stat.toNumber() : 0
})
counts.countInvites = counts.countEmails - counts.countUsers
return counts
} finally {
session.close()
statistics: async (_parent, _args, context: Context) => {
const statistics = {
users: 0,
usersDeleted: 0,
posts: 0,
comments: 0,
notifications: 0,
emails: 0,
follows: 0,
shouts: 0,
invites: 0,
chatMessages: 0,
chatRooms: 0,
tags: 0,
locations: 0,
groups: 0,
inviteCodes: 0,
inviteCodesExpired: 0,
inviteCodesRedeemed: 0,
badgesRewarded: 0,
badgesDisplayed: 0,
usersVerified: 0,
reports: 0,
}
const [metaStats] = (
await context.database.query({
query: `CALL apoc.meta.stats() YIELD labels, relTypesCount
RETURN labels, relTypesCount`,
})
).records.map((record) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return { ...record.get('labels'), ...record.get('relTypesCount') }
})
const deletedUsers = parseInt(
(
await context.database.query({
query: `MATCH (u:User) WHERE NOT (u)-[:PRIMARY_EMAIL]->(:EmailAddress) RETURN toString(count(u)) AS count`,
})
).records[0].get('count') as string,
)
const invalidInviteCodes = parseInt(
(
await context.database.query({
query: `MATCH (i:InviteCode) WHERE NOT i.expiresAt IS NULL OR i.expiresAt >= datetime() RETURN toString(count(i)) AS count`,
})
).records[0].get('count') as string,
)
statistics.users = (metaStats['User']?.toNumber() ?? 0) - deletedUsers
statistics.usersDeleted = deletedUsers
statistics.posts = metaStats['Post']?.toNumber() ?? 0
statistics.comments = metaStats['Comment']?.toNumber() ?? 0
statistics.notifications = metaStats['NOTIFIED']?.toNumber() ?? 0
statistics.emails = metaStats['EmailAddress']?.toNumber() ?? 0
statistics.follows = metaStats['FOLLOWS']?.toNumber() ?? 0
statistics.shouts = metaStats['SHOUTED']?.toNumber() ?? 0
statistics.invites = statistics.emails - statistics.users
statistics.chatMessages = metaStats['Message']?.toNumber() ?? 0
statistics.chatRooms = metaStats['Room']?.toNumber() ?? 0
statistics.tags = metaStats['Tag']?.toNumber() ?? 0
statistics.locations = metaStats['Location']?.toNumber() ?? 0
statistics.groups = metaStats['Group']?.toNumber() ?? 0
statistics.inviteCodes = (metaStats['InviteCode']?.toNumber() ?? 0) - invalidInviteCodes
statistics.inviteCodesExpired = invalidInviteCodes
statistics.inviteCodesRedeemed = metaStats['REDEEMED']?.toNumber() ?? 0
statistics.badgesRewarded = metaStats['REWARDED']?.toNumber() ?? 0
statistics.badgesDisplayed = metaStats['SELECTED']?.toNumber() ?? 0
statistics.usersVerified = metaStats['VERIFIES']?.toNumber() ?? 0
statistics.reports = metaStats['Report']?.toNumber() ?? 0
return statistics
},
},
}

View File

@ -3,12 +3,26 @@ type Query {
}
type Statistics {
countUsers: Int!
countPosts: Int!
countComments: Int!
countNotifications: Int!
countInvites: Int!
countFollows: Int!
countShouts: Int!
users: Int!
usersDeleted: Int!
posts: Int!
comments: Int!
notifications: Int!
emails: Int!
follows: Int!
shouts: Int!
invites: Int!
chatMessages: Int!
chatRooms: Int!
tags: Int!
locations: Int!
groups: Int!
inviteCodes: Int!
inviteCodesExpired: Int!
inviteCodesRedeemed: Int!
badgesRewarded: Int!
badgesDisplayed: Int!
usersVerified: Int!
reports: Int!
}

File diff suppressed because it is too large Load Diff

View File

@ -21,4 +21,4 @@ version: 0.1.0
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "3.6.0"
appVersion: "3.6.1"

View File

@ -21,4 +21,4 @@ version: 0.1.0
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "3.6.0"
appVersion: "3.6.1"

View File

@ -1,35 +0,0 @@
# Todo: !!! This file seems related to our old maintenance worker for MongoDB and has to be refactored in case of using it !!!
services:
maintenance-worker:
image: ghcr.io/ocelot-social-community/ocelot-social/develop-maintenance-worker:latest
build:
context: deployment/legacy-migration/maintenance-worker
volumes:
- uploads:/uploads
- neo4j-data:/data
- ./deployment/legacy-migration/maintenance-worker/migration/:/migration
- ./deployment/legacy-migration/maintenance-worker/ssh/:/root/.ssh
environment:
- NEO4J_dbms_security_auth__enabled=false
- NEO4J_dbms_memory_heap_max__size=2G
- GRAPHQL_URI=http://localhost:4000
- CLIENT_URI=http://localhost:3000
- JWT_SECRET=b/&&7b78BF&fv/Vd
- PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78
- NEO4J_apoc_import_file_enabled=true
- "SSH_USERNAME=${SSH_USERNAME}"
- "SSH_HOST=${SSH_HOST}"
- "MONGODB_USERNAME=${MONGODB_USERNAME}"
- "MONGODB_PASSWORD=${MONGODB_PASSWORD}"
- "MONGODB_AUTH_DB=${MONGODB_AUTH_DB}"
- "MONGODB_DATABASE=${MONGODB_DATABASE}"
- "UPLOADS_DIRECTORY=${UPLOADS_DIRECTORY}"
- "MONGO_EXPORT_SPLIT_SIZE=${MONGO_EXPORT_SPLIT_SIZE}"
ports:
- 7687:7687
- 7474:7474
volumes:
neo4j-data:
uploads:

View File

@ -1,12 +1,12 @@
{
"name": "ocelot-social-frontend",
"version": "3.6.0",
"version": "3.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ocelot-social-frontend",
"version": "3.6.0",
"version": "3.6.1",
"license": "Apache-2.0",
"dependencies": {
"@intlify/unplugin-vue-i18n": "^2.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "ocelot-social-frontend",
"version": "3.6.0",
"version": "3.6.1",
"description": "ocelot.social new Frontend (in development and not fully implemented) by IT4C Boilerplate for frontends",
"main": "build/index.js",
"type": "module",

12
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "ocelot-social",
"version": "3.4.0",
"version": "3.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ocelot-social",
"version": "3.4.0",
"version": "3.6.1",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.27.1",
@ -15,7 +15,7 @@
"@badeball/cypress-cucumber-preprocessor": "^22.0.1",
"@cucumber/cucumber": "11.2.0",
"@cypress/browserify-preprocessor": "^3.0.2",
"@faker-js/faker": "9.7.0",
"@faker-js/faker": "9.8.0",
"auto-changelog": "^2.5.0",
"bcryptjs": "^3.0.2",
"cross-env": "^7.0.3",
@ -2849,9 +2849,9 @@
}
},
"node_modules/@faker-js/faker": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz",
"integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==",
"version": "9.8.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.8.0.tgz",
"integrity": "sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==",
"dev": true,
"funding": [
{

View File

@ -1,6 +1,6 @@
{
"name": "ocelot-social",
"version": "3.6.0",
"version": "3.6.1",
"description": "Free and open source software program code available to run social networks.",
"author": "ocelot.social Community",
"license": "MIT",
@ -39,7 +39,7 @@
"@badeball/cypress-cucumber-preprocessor": "^22.0.1",
"@cucumber/cucumber": "11.2.0",
"@cypress/browserify-preprocessor": "^3.0.2",
"@faker-js/faker": "9.7.0",
"@faker-js/faker": "9.8.0",
"auto-changelog": "^2.5.0",
"bcryptjs": "^3.0.2",
"cross-env": "^7.0.3",

View File

@ -1,6 +1,10 @@
@import './imports/_tooltip.scss';
@import './imports/_toast.scss';
html {
scrollbar-gutter: stable;
}
// Transition Easing
$easeOut: cubic-bezier(0.19, 1, 0.22, 1);
@ -145,7 +149,6 @@ hr {
body.dropdown-open {
max-height: 100vh;
overflow: hidden;
scrollbar-gutter: stable;
}
.base-card > .ds-section {

View File

@ -4,6 +4,7 @@ exports[`GroupContentMenu renders as groupProfile when I am the owner 1`] = `
<div>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu"
container="body"
delay="0"
@ -121,6 +122,7 @@ exports[`GroupContentMenu renders as groupProfile, muted 1`] = `
<div>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu"
container="body"
delay="0"
@ -196,6 +198,7 @@ exports[`GroupContentMenu renders as groupProfile, not muted 1`] = `
<div>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu"
container="body"
delay="0"
@ -271,6 +274,7 @@ exports[`GroupContentMenu renders as groupTeaser 1`] = `
<div>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu"
container="body"
delay="0"

View File

@ -6,6 +6,7 @@
:disabled="disabled"
trigger="manual"
:offset="offset"
boundaries-element="body"
>
<slot :toggleMenu="toggleMenu" :openMenu="openMenu" :closeMenu="closeMenu" :isOpen="isOpen" />
<div slot="popover" @mouseover="popoverMouseEnter" @mouseleave="popoverMouseLeave">
@ -72,7 +73,7 @@ export default {
}
},
closeMenu(useTimeout) {
if (this.disabled) {
if (this.noMouseLeaveClosing || this.disabled) {
return
}
this.clearTimeouts()

View File

@ -15,6 +15,11 @@ const stubs = {
}
const authUserMock = jest.fn().mockReturnValue({ activeCategories: [] })
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
Category: [{ id: 'cat0' }, { id: 'cat1' }, { id: 'cat2' }, { id: 'cat3' }, { id: 'cat4' }],
},
})
describe('LoginForm', () => {
let mocks
@ -47,6 +52,9 @@ describe('LoginForm', () => {
success: jest.fn(),
error: jest.fn(),
},
$apollo: {
query: apolloQueryMock,
},
}
return mount(LoginForm, { mocks, localVue, propsData, store, stubs })
}
@ -74,7 +82,9 @@ describe('LoginForm', () => {
describe('no categories saved', () => {
it('resets the categories', async () => {
await fillIn(Wrapper())
const wrapper = Wrapper()
await fillIn(wrapper)
await wrapper.vm.$nextTick()
expect(storeMocks.mutations['posts/RESET_CATEGORIES']).toHaveBeenCalled()
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).not.toHaveBeenCalled()
})
@ -82,13 +92,28 @@ describe('LoginForm', () => {
describe('categories saved', () => {
it('sets the categories', async () => {
authUserMock.mockReturnValue({ activeCategories: ['cat1', 'cat9', 'cat12'] })
await fillIn(Wrapper())
authUserMock.mockReturnValue({ activeCategories: ['cat0', 'cat2', 'cat4'] })
const wrapper = Wrapper()
await fillIn(wrapper)
await wrapper.vm.$nextTick()
expect(storeMocks.mutations['posts/RESET_CATEGORIES']).toHaveBeenCalled()
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledTimes(3)
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat1')
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat9')
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat12')
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat0')
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat2')
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4')
})
})
describe('all categories saved', () => {
it('resets the categories', async () => {
authUserMock.mockReturnValue({
activeCategories: ['cat0', 'cat1', 'cat2', 'cat3', 'cat4', 'cat5'],
})
const wrapper = Wrapper()
await fillIn(wrapper)
await wrapper.vm.$nextTick()
expect(storeMocks.mutations['posts/RESET_CATEGORIES']).toHaveBeenCalled()
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).not.toHaveBeenCalled()
})
})
})

View File

@ -59,6 +59,7 @@ import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Logo from '~/components/Logo/Logo'
import ShowPassword from '../ShowPassword/ShowPassword.vue'
import { mapGetters, mapMutations } from 'vuex'
import CategoryQuery from '~/graphql/CategoryQuery'
export default {
components: {
@ -98,9 +99,13 @@ export default {
const { email, password } = this.form
try {
await this.$store.dispatch('auth/login', { email, password })
const result = await this.$apollo.query({
query: CategoryQuery(),
})
const categories = result.data.Category
if (this.currentUser && this.currentUser.activeCategories) {
this.resetCategories()
if (this.currentUser.activeCategories.length < 19) {
if (this.currentUser.activeCategories.length < categories.length) {
this.currentUser.activeCategories.forEach((categoryId) => {
this.toggleCategory(categoryId)
})

View File

@ -171,31 +171,27 @@ export default {
}
},
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the entire view has been rendered
this.formData.name = this.sliderData.collectedInputData.name
? this.sliderData.collectedInputData.name
: ''
this.formData.password = this.sliderData.collectedInputData.password
? this.sliderData.collectedInputData.password
: ''
this.formData.passwordConfirmation = this.sliderData.collectedInputData.passwordConfirmation
? this.sliderData.collectedInputData.passwordConfirmation
: ''
this.termsAndConditionsConfirmed = this.sliderData.collectedInputData
.termsAndConditionsConfirmed
? this.sliderData.collectedInputData.termsAndConditionsConfirmed
: false
this.recieveCommunicationAsEmailsEtcConfirmed = this.sliderData.collectedInputData
.recieveCommunicationAsEmailsEtcConfirmed
? this.sliderData.collectedInputData.recieveCommunicationAsEmailsEtcConfirmed
: false
this.sendValidation()
this.formData.name = this.sliderData.collectedInputData.name
? this.sliderData.collectedInputData.name
: ''
this.formData.password = this.sliderData.collectedInputData.password
? this.sliderData.collectedInputData.password
: ''
this.formData.passwordConfirmation = this.sliderData.collectedInputData.passwordConfirmation
? this.sliderData.collectedInputData.passwordConfirmation
: ''
this.termsAndConditionsConfirmed = this.sliderData.collectedInputData
.termsAndConditionsConfirmed
? this.sliderData.collectedInputData.termsAndConditionsConfirmed
: false
this.recieveCommunicationAsEmailsEtcConfirmed = this.sliderData.collectedInputData
.recieveCommunicationAsEmailsEtcConfirmed
? this.sliderData.collectedInputData.recieveCommunicationAsEmailsEtcConfirmed
: false
this.sendValidation()
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
},
computed: {

View File

@ -67,20 +67,16 @@ export default {
}
},
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the entire view has been rendered
this.formData.email = this.sliderData.collectedInputData.email
? this.sliderData.collectedInputData.email
: ''
this.sendValidation()
this.formData.email = this.sliderData.collectedInputData.email
? this.sliderData.collectedInputData.email
: ''
this.sendValidation()
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: {
...this.buttonValues().sliderSettings,
buttonSliderCallback: this.onNextClick,
},
})
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: {
...this.buttonValues().sliderSettings,
buttonSliderCallback: this.onNextClick,
},
})
},
watch: {

View File

@ -75,17 +75,13 @@ export default {
}
},
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the entire view has been rendered
this.formData.inviteCode = this.sliderData.collectedInputData.inviteCode
? this.sliderData.collectedInputData.inviteCode
: ''
this.sendValidation()
this.formData.inviteCode = this.sliderData.collectedInputData.inviteCode
? this.sliderData.collectedInputData.inviteCode
: ''
this.sendValidation()
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
},
computed: {
@ -96,12 +92,14 @@ export default {
return this.formData.inviteCode.length === 6
},
invitedBy() {
return this.sliderData.sliders[this.sliderIndex].data.response.validateInviteCode
return this.validInput &&
this.sliderData.sliders[this.sliderIndex].data.response.validateInviteCode
? this.sliderData.sliders[this.sliderIndex].data.response.validateInviteCode.generatedBy
: null
},
invitedTo() {
return this.sliderData.sliders[this.sliderIndex].data.response.validateInviteCode
return this.validInput &&
this.sliderData.sliders[this.sliderIndex].data.response.validateInviteCode
? this.sliderData.sliders[this.sliderIndex].data.response.validateInviteCode.invitedTo
: null
},
@ -124,18 +122,11 @@ export default {
async handleInputValid() {
this.sendValidation()
},
isVariablesRequested(variables) {
return (
this.sliderData.sliders[this.sliderIndex].data.request &&
this.sliderData.sliders[this.sliderIndex].data.request.variables &&
this.sliderData.sliders[this.sliderIndex].data.request.variables.code === variables.code
)
},
async handleSubmitVerify() {
const { inviteCode } = this.sliderData.collectedInputData
const variables = { code: inviteCode }
if (!this.isVariablesRequested(variables) && !this.dbRequestInProgress) {
if (!this.dbRequestInProgress) {
try {
this.dbRequestInProgress = true

View File

@ -18,12 +18,8 @@ export default {
sliderData: { type: Object, required: true },
},
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the entire view has been rendered
this.sliderData.setSliderValuesCallback(true, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
this.sliderData.setSliderValuesCallback(true, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
},
methods: {

View File

@ -66,17 +66,13 @@ export default {
}
},
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the entire view has been rendered
this.formData.nonce = this.sliderData.collectedInputData.nonce
? this.sliderData.collectedInputData.nonce
: ''
this.sendValidation()
this.formData.nonce = this.sliderData.collectedInputData.nonce
? this.sliderData.collectedInputData.nonce
: ''
this.sendValidation()
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
this.sliderData.setSliderValuesCallback(this.validInput, {
sliderSettings: { buttonSliderCallback: this.onNextClick },
})
},
computed: {
@ -109,20 +105,11 @@ export default {
async handleInputValid() {
this.sendValidation()
},
isVariablesRequested(variables) {
return (
this.sliderData.sliders[this.sliderIndex].data.request &&
this.sliderData.sliders[this.sliderIndex].data.request.variables &&
this.sliderData.sliders[this.sliderIndex].data.request.variables.email ===
variables.email &&
this.sliderData.sliders[this.sliderIndex].data.request.variables.nonce === variables.nonce
)
},
async handleSubmitVerify() {
const { email, nonce } = this.sliderData.collectedInputData
const variables = { email, nonce }
if (!this.isVariablesRequested(variables) && !this.dbRequestInProgress) {
if (!this.dbRequestInProgress) {
try {
this.dbRequestInProgress = true

View File

@ -86,6 +86,12 @@ describe('UserTeaser', () => {
},
mocks: {
$t: jest.fn((t) => t),
$i18n: {
locale: jest.fn(() => 'en'),
},
$apollo: {
query: jest.fn(() => Promise.resolve({ data: { user } })),
},
},
})
}

View File

@ -6,7 +6,7 @@
:link-to-profile="linkToProfile"
:show-popover="showPopover"
:user-link="userLink"
@open-menu="openMenu(false)"
@open-menu="loadPopover(openMenu)"
@close-menu="closeMenu(false)"
data-test="avatarUserLink"
>
@ -18,7 +18,7 @@
:link-to-profile="linkToProfile"
:show-popover="showPopover"
:user-link="userLink"
@open-menu="openMenu(false)"
@open-menu="loadPopover(openMenu)"
@close-menu="closeMenu(false)"
>
<span class="slug">{{ userSlug }}</span>
@ -57,6 +57,7 @@
<script>
import { mapGetters } from 'vuex'
import { userTeaserQuery } from '~/graphql/User.js'
import DateTime from '~/components/DateTime'
import Dropdown from '~/components/Dropdown'
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
@ -119,5 +120,15 @@ export default {
return name || this.$t('profile.userAnonym')
},
},
methods: {
async loadPopover(openMenu) {
// Load user data if not already loaded, to avoid flickering
await this.$apollo.query({
query: userTeaserQuery(this.$i18n),
variables: { id: this.user.id },
})
openMenu(false)
},
},
}
</script>

View File

@ -1,6 +1,5 @@
<template>
<div class="placeholder" v-if="!user" />
<div class="user-teaser-popover" v-else>
<div class="user-teaser-popover">
<badges
v-if="$env.BADGES_ENABLED && user.badgeVerification"
:badges="[user.badgeVerification, ...user.badgeTrophiesSelected]"
@ -67,10 +66,6 @@ export default {
</script>
<style scoped>
.placeholder {
height: 200px;
width: 200px;
}
.user-teaser-popover {
display: flex;
flex-direction: column;

View File

@ -9,6 +9,7 @@ exports[`UserTeaser given an user avatar is disabled does not render the avatar
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -135,6 +136,7 @@ exports[`UserTeaser given an user user is disabled current user is a moderator r
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -250,6 +252,7 @@ exports[`UserTeaser given an user with linkToProfile, on desktop renders 1`] = `
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -333,6 +336,7 @@ exports[`UserTeaser given an user with linkToProfile, on desktop when hovering t
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -422,6 +426,7 @@ exports[`UserTeaser given an user with linkToProfile, on touch screen renders 1`
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -505,6 +510,7 @@ exports[`UserTeaser given an user with linkToProfile, on touch screen when click
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -594,6 +600,7 @@ exports[`UserTeaser given an user without linkToProfile, on desktop renders 1`]
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -677,6 +684,7 @@ exports[`UserTeaser given an user without linkToProfile, on desktop when hoverin
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -765,6 +773,7 @@ exports[`UserTeaser given an user without linkToProfile, on desktop when hoverin
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -853,6 +862,7 @@ exports[`UserTeaser given an user without linkToProfile, on touch screen renders
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -936,6 +946,7 @@ exports[`UserTeaser given an user without linkToProfile, on touch screen when cl
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -1024,6 +1035,7 @@ exports[`UserTeaser given an user without linkToProfile, on touch screen when cl
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"

View File

@ -40,11 +40,17 @@ const options = {
NETWORK_NAME: process.env.NETWORK_NAME || 'Ocelot.social',
}
const language = {
LANGUAGE_DEFAULT: process.env.LANGUAGE_DEFAULT || 'en',
LANGUAGE_FALLBACK: process.env.LANGUAGE_FALLBACK || 'en',
}
const CONFIG = {
...environment,
...server,
...sentry,
...options,
...language,
}
// override process.env with the values here since they contain default values

View File

@ -3,13 +3,27 @@ import gql from 'graphql-tag'
export const Statistics = gql`
query {
statistics {
countUsers
countPosts
countComments
countNotifications
countInvites
countFollows
countShouts
users
usersDeleted
posts
comments
notifications
emails
follows
shouts
invites
chatMessages
chatRooms
tags
locations
groups
inviteCodes
inviteCodesExpired
inviteCodesRedeemed
badgesRewarded
badgesDisplayed
usersVerified
reports
}
}
`

View File

@ -39,16 +39,28 @@
"postCount": "Beiträge"
},
"dashboard": {
"badgesDisplayed": "Badges ausgestellt",
"badgesRewarded": "Badges verteilt",
"chatMessages": "Chat-Nachrichten",
"chatRooms": "Chat-Räume",
"comments": "Kommentare",
"follows": "Folgen",
"emails": "E-Mails",
"follows": "Follows",
"groups": "Gruppen",
"inviteCodes": "Einladungslinks",
"inviteCodesExpired": "Abgelaufene Einladungslinks",
"inviteCodesRedeemed": "Eingelöste Einladungslinks",
"invites": "Einladungen",
"locations": "Orte",
"name": "Startzentrale",
"notifications": "Benachrichtigungen",
"organizations": "Organisationen",
"posts": "Beiträge",
"projects": "Projekte",
"reports": "Gemeldet",
"shouts": "Empfehlungen",
"users": "Nutzer"
"tags": "Tags",
"users": "Nutzer",
"usersDeleted": "Gelöschte Nutzer",
"usersVerified": "Verifizierte Nutzer"
},
"donations": {
"goal": "Monatlich benötigte Spenden",
@ -246,7 +258,7 @@
}
},
"invited-by": "Eingeladen von {invitedBy}",
"invited-by-and-to": "Einladung von {invitedBy} zur Grupppe {invitedTo}",
"invited-by-and-to": "Einladung von {invitedBy} zur Gruppe {invitedTo}",
"invited-to-hidden-group": "Eingeladen von {invitedBy} zu einer versteckten Gruppe"
},
"no-public-registrstion": {

View File

@ -39,16 +39,28 @@
"postCount": "Posts"
},
"dashboard": {
"badgesDisplayed": "Badges Displayed",
"badgesRewarded": "Badges Rewarded",
"chatMessages": "Chat Messages",
"chatRooms": "Chat Rooms",
"comments": "Comments",
"emails": "E-Mails",
"follows": "Follows",
"groups": "Groups",
"inviteCodes": "Invite Codes",
"inviteCodesExpired": "Expired Invite Codes",
"inviteCodesRedeemed": "Redeemed Invite Codes",
"invites": "Invites",
"locations": "Locations",
"name": "Dashboard",
"notifications": "Notifications",
"organizations": "Organizations",
"posts": "Posts",
"projects": "Projects",
"reports": "Reports",
"shouts": "Shouts",
"users": "Users"
"tags": "Tags",
"users": "Users",
"usersDeleted": "Users deleted",
"usersVerified": "Users verified"
},
"donations": {
"goal": "Monthly donations needed",

View File

@ -39,16 +39,28 @@
"postCount": "Contribuciones"
},
"dashboard": {
"badgesDisplayed": null,
"badgesRewarded": null,
"chatMessages": null,
"chatRooms": null,
"comments": "Comentarios",
"emails": null,
"follows": "Sigue",
"groups": null,
"inviteCodes": null,
"inviteCodesExpired": null,
"inviteCodesRedeemed": null,
"invites": "Invita",
"locations": null,
"name": "Tablero",
"notifications": "Notificaciones",
"organizations": "Organizaciones",
"posts": "Contribuciones",
"projects": "Proyectos",
"reports": null,
"shouts": "Recomendaciones",
"users": "Usuarios"
"tags": null,
"users": "Usuarios",
"usersDeleted": null,
"usersVerified": null
},
"donations": {
"goal": "Donaciones mensuales necesarias",

View File

@ -39,16 +39,28 @@
"postCount": "Postes"
},
"dashboard": {
"badgesDisplayed": null,
"badgesRewarded": null,
"chatMessages": null,
"chatRooms": null,
"comments": "Commentaires",
"emails": null,
"follows": "Suit",
"groups": null,
"inviteCodes": null,
"inviteCodesExpired": null,
"inviteCodesRedeemed": null,
"invites": "Invitations",
"locations": null,
"name": "Tableau de bord",
"notifications": "Notifications",
"organizations": "Organisations",
"posts": "Postes",
"projects": "Projets",
"reports": null,
"shouts": "Cris",
"users": "Utilisateurs"
"tags": null,
"users": "Utilisateurs",
"usersDeleted": null,
"usersVerified": null
},
"donations": {
"goal": "Dons mensuels requis",

View File

@ -39,16 +39,28 @@
"postCount": "Messaggi"
},
"dashboard": {
"badgesDisplayed": null,
"badgesRewarded": null,
"chatMessages": null,
"chatRooms": null,
"comments": "Commenti",
"emails": null,
"follows": "Segue",
"groups": null,
"inviteCodes": null,
"inviteCodesExpired": null,
"inviteCodesRedeemed": null,
"invites": "Inviti",
"locations": null,
"name": "Cruscotto",
"notifications": "Notifiche",
"organizations": "Organizzazioni",
"posts": "Messaggi",
"projects": "Progetti",
"reports": null,
"shouts": "Gridi",
"users": "Utenti"
"tags": null,
"users": "Utenti",
"usersDeleted": null,
"usersVerified": null
},
"donations": {
"goal": "Donazioni mensili necessarie",

View File

@ -39,16 +39,28 @@
"postCount": "Berichten"
},
"dashboard": {
"badgesDisplayed": null,
"badgesRewarded": null,
"chatMessages": null,
"chatRooms": null,
"comments": "Opmerkingen",
"emails": null,
"follows": "Volgt",
"groups": null,
"inviteCodes": null,
"inviteCodesExpired": null,
"inviteCodesRedeemed": null,
"invites": "Uitnodigingen",
"locations": null,
"name": "Dashboard",
"notifications": "Meldingen",
"organizations": "Organisaties",
"posts": "Berichten",
"projects": "Projecten",
"reports": null,
"shouts": "Shouts",
"users": "Gebruikers"
"tags": null,
"users": "Gebruikers",
"usersDeleted": null,
"usersVerified": null
},
"donations": {
"goal": null,

View File

@ -39,16 +39,28 @@
"postCount": "Stanowiska"
},
"dashboard": {
"badgesDisplayed": null,
"badgesRewarded": null,
"chatMessages": null,
"chatRooms": null,
"comments": "Komentarze",
"emails": null,
"follows": "Podąża za",
"groups": null,
"inviteCodes": null,
"inviteCodesExpired": null,
"inviteCodesRedeemed": null,
"invites": "Zaprasza",
"locations": null,
"name": "Tablica rozdzielcza",
"notifications": "Powiadomienia",
"organizations": "Organizacje",
"posts": "Stanowiska",
"projects": "Projekty",
"reports": null,
"shouts": "Zalecane",
"users": "Użytkownicy"
"tags": null,
"users": "Użytkownicy",
"usersDeleted": null,
"usersVerified": null
},
"donations": {
"goal": null,

View File

@ -39,16 +39,28 @@
"postCount": "Postagens"
},
"dashboard": {
"badgesDisplayed": null,
"badgesRewarded": null,
"chatMessages": null,
"chatRooms": null,
"comments": "Comentários",
"emails": null,
"follows": "Segue",
"groups": null,
"inviteCodes": null,
"inviteCodesExpired": null,
"inviteCodesRedeemed": null,
"invites": "Convites",
"locations": null,
"name": "Painel de controle",
"notifications": "Notificações",
"organizations": "Organizações",
"posts": "Postagens",
"projects": "Projetos",
"reports": null,
"shouts": "Aclamações",
"users": "Usuários"
"tags": null,
"users": "Usuários",
"usersDeleted": null,
"usersVerified": null
},
"donations": {
"goal": "Doações mensais necessárias",

View File

@ -39,16 +39,28 @@
"postCount": "Посты"
},
"dashboard": {
"badgesDisplayed": null,
"badgesRewarded": null,
"chatMessages": null,
"chatRooms": null,
"comments": "Комментарии",
"emails": null,
"follows": "Подписки",
"groups": null,
"inviteCodes": null,
"inviteCodesExpired": null,
"inviteCodesRedeemed": null,
"invites": "Приглашения",
"locations": null,
"name": "Панель управления",
"notifications": "Уведомления",
"organizations": "Организации",
"posts": "Посты",
"projects": "Проекты",
"reports": null,
"shouts": "Выкрики",
"users": "Пользователи"
"tags": null,
"users": "Пользователи",
"usersDeleted": null,
"usersVerified": null
},
"donations": {
"goal": "Необходимы ежемесячные пожертвования",

View File

@ -1,6 +1,6 @@
{
"name": "@ocelot-social/maintenance",
"version": "3.6.0",
"version": "3.6.1",
"description": "Maintenance page for ocelot.social",
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
"author": "ocelot.social Community",

View File

@ -1,6 +1,6 @@
{
"name": "ocelot-social-webapp",
"version": "3.6.0",
"version": "3.6.1",
"description": "ocelot.social Frontend",
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
"author": "ocelot.social Community",
@ -92,7 +92,7 @@
"eslint-plugin-import": "~2.31.0",
"eslint-plugin-jest": "~24.4.0",
"eslint-plugin-node": "~11.1.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-promise": "~7.2.1",
"eslint-plugin-standard": "~5.0.0",
"eslint-plugin-vue": "~9.33.0",

View File

@ -20,100 +20,20 @@
<template v-else-if="data">
<ds-space margin="large">
<ds-flex>
<ds-flex-item :width="{ base: '100%', sm: '50%', md: '33%' }">
<ds-flex-item
v-for="(value, name, index) in filterStatistics(data.statistics)"
:key="index"
:width="{ base: '100%', sm: '50%', md: '33%' }"
>
<ds-space margin="small">
<ds-number
:count="0"
:label="$t('admin.dashboard.users')"
:label="$t('admin.dashboard.' + name)"
size="x-large"
uppercase
>
<client-only slot="count">
<hc-count-to :end-val="data.statistics.countUsers" />
</client-only>
</ds-number>
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%', md: '33%' }">
<ds-space margin="small">
<ds-number
:count="0"
:label="$t('admin.dashboard.posts')"
size="x-large"
uppercase
>
<client-only slot="count">
<hc-count-to :end-val="data.statistics.countPosts" />
</client-only>
</ds-number>
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%', md: '33%' }">
<ds-space margin="small">
<ds-number
:count="0"
:label="$t('admin.dashboard.comments')"
size="x-large"
uppercase
>
<client-only slot="count">
<hc-count-to :end-val="data.statistics.countComments" />
</client-only>
</ds-number>
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%', md: '33%' }">
<ds-space margin="small">
<ds-number
:count="0"
:label="$t('admin.dashboard.notifications')"
size="x-large"
uppercase
>
<client-only slot="count">
<hc-count-to :end-val="data.statistics.countNotifications" />
</client-only>
</ds-number>
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%', md: '33%' }">
<ds-space margin="small">
<ds-number
:count="0"
:label="$t('admin.dashboard.invites')"
size="x-large"
uppercase
>
<client-only slot="count">
<hc-count-to :end-val="data.statistics.countInvites" />
</client-only>
</ds-number>
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%', md: '33%' }">
<ds-space margin="small">
<ds-number
:count="0"
:label="$t('admin.dashboard.follows')"
size="x-large"
uppercase
>
<client-only slot="count">
<hc-count-to :end-val="data.statistics.countFollows" />
</client-only>
</ds-number>
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%', md: '33%' }">
<ds-space margin="small">
<ds-number
:count="0"
:label="$t('admin.dashboard.shouts')"
size="x-large"
uppercase
>
<client-only slot="count">
<hc-count-to :end-val="data.statistics.countShouts" />
<hc-count-to :end-val="value" />
</client-only>
</ds-number>
</ds-space>
@ -140,5 +60,11 @@ export default {
Statistics,
}
},
methods: {
filterStatistics(data) {
delete data.__typename
return data
},
},
}
</script>

View File

@ -67,6 +67,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu group-profile-content-menu"
container="body"
delay="0"
@ -517,6 +518,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -597,6 +599,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -677,6 +680,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -757,6 +761,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -1981,6 +1986,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu group-profile-content-menu"
container="body"
delay="0"
@ -2388,6 +2394,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -2468,6 +2475,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -2548,6 +2556,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -2628,6 +2637,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -2946,6 +2956,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu group-profile-content-menu"
container="body"
delay="0"
@ -3362,6 +3373,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -3442,6 +3454,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -3522,6 +3535,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -3602,6 +3616,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -4181,6 +4196,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -4261,6 +4277,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -4341,6 +4358,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -4421,6 +4439,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -4998,6 +5017,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -5078,6 +5098,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -5158,6 +5179,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -5238,6 +5260,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -5511,6 +5534,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu group-profile-content-menu"
container="body"
delay="0"
@ -5884,6 +5908,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -5964,6 +5989,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -6044,6 +6070,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -6124,6 +6151,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -6447,6 +6475,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu group-profile-content-menu"
container="body"
delay="0"
@ -6905,6 +6934,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -6985,6 +7015,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -7065,6 +7096,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -7145,6 +7177,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -7464,6 +7497,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="group-content-menu group-profile-content-menu"
container="body"
delay="0"
@ -7879,6 +7913,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -7959,6 +7994,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -8039,6 +8075,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"
@ -8119,6 +8156,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="user-teaser"
container="body"
delay="0"

View File

@ -37,6 +37,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="content-menu user-content-menu"
container="body"
delay="0"
@ -655,6 +656,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="content-menu user-content-menu"
container="body"
delay="0"
@ -1338,6 +1340,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="content-menu user-content-menu"
container="body"
delay="0"
@ -1901,6 +1904,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
<client-only-stub>
<v-popover-stub
autohide="true"
boundarieselement="body"
class="content-menu user-content-menu"
container="body"
delay="0"

View File

@ -48,6 +48,7 @@ export default ({ app, req, cookie, store }) => {
})
}
/*
const user = store.getters['auth/user']
const token = store.getters['auth/token']
// persist language if it differs from last value
@ -57,54 +58,47 @@ export default ({ app, req, cookie, store }) => {
// uiLanguage: localeInStore
// }, { root: true })
}
*/
}
// const i18nStore = new Vuex.Store({
// strict: debug
// })
Vue.use(vuexI18n.plugin, store, {
onTranslationNotFound: function (locale, key) {
if (debug) {
onTranslationNotFound:
debug &&
function (locale, key) {
/* eslint-disable-next-line no-console */
console.warn(`vuex-i18n :: Key '${key}' not found for locale '${locale}'`)
}
},
},
})
let userLocale = 'en'
let userLocale = app.$env.LANGUAGE_DEFAULT
const localeCookie = app.$cookies.get(key)
/* const userSettings = store.getters['auth/userSettings']
if (userSettings && userSettings.uiLanguage) {
// try to get saved user preference
userLocale = userSettings.uiLanguage
} else */
if (!isEmpty(localeCookie)) {
userLocale = localeCookie
} else {
try {
userLocale = process.browser
? navigator.language || navigator.userLanguage
: req.headers['accept-language'].split(',')[0]
userLocale = (
process.browser
? navigator.language || navigator.userLanguage
: req.headers['accept-language'].split(',')[0]
).substr(0, 2)
} catch (err) {}
if (userLocale && !isEmpty(userLocale.language)) {
userLocale = userLocale.language.substr(0, 2)
}
}
const availableLocales = locales.filter((lang) => !!lang.enabled)
const locale = find(availableLocales, ['code', userLocale]) ? userLocale : 'en'
const locale = find(availableLocales, ['code', userLocale])
? userLocale
: app.$env.LANGUAGE_DEFAULT
// register the fallback locales
registerTranslation({ Vue, locale: 'en' })
if (locale !== 'en') {
// register locales
registerTranslation({ Vue, locale: app.$env.LANGUAGE_FALLBACK })
if (locale !== app.$env.LANGUAGE_FALLBACK) {
registerTranslation({ Vue, locale })
}
// Set the start locale to use
Vue.i18n.set(locale)
Vue.i18n.fallback('en')
Vue.i18n.fallback(app.$env.LANGUAGE_FALLBACK)
if (process.browser) {
store.subscribe((mutation) => {

View File

@ -9488,10 +9488,10 @@ eslint-plugin-node@~11.1.0:
resolve "^1.10.1"
semver "^6.1.0"
eslint-plugin-prettier@^5.2.6:
version "5.2.6"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz#be39e3bb23bb3eeb7e7df0927cdb46e4d7945096"
integrity sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==
eslint-plugin-prettier@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz#54d4748904e58eaf1ffe26c4bffa4986ca7f952b"
integrity sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==
dependencies:
prettier-linter-helpers "^1.0.0"
synckit "^0.11.0"

View File

@ -1542,10 +1542,10 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699"
integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==
"@faker-js/faker@9.7.0":
version "9.7.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.7.0.tgz#1cf1fecfcad5e2da2332140bf3b5f23cc1c2a7f4"
integrity sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==
"@faker-js/faker@9.8.0":
version "9.8.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.8.0.tgz#3344284028d1c9dc98dee2479f82939310370d88"
integrity sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==
"@fastify/busboy@^2.0.0":
version "2.1.1"