mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-01-20 20:01:25 +00:00
Merge branch 'master' into brand-reformer-network-first-step
This commit is contained in:
commit
46ec1c4344
2
.github/workflows/docker-push.yml
vendored
2
.github/workflows/docker-push.yml
vendored
@ -81,7 +81,7 @@ jobs:
|
||||
type=sha
|
||||
- name: Build and push Docker images
|
||||
id: push
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0
|
||||
with:
|
||||
context: ${{ matrix.app.context }}
|
||||
target: ${{ matrix.app.target }}
|
||||
|
||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@ -64,7 +64,7 @@ jobs:
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
#- name: Repository Dispatch
|
||||
# uses: peter-evans/repository-dispatch@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
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@ -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)
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
@ -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',
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
531
backend/src/emails/__snapshots__/supportLine.spec.ts.snap
Normal file
531
backend/src/emails/__snapshots__/supportLine.spec.ts.snap
Normal 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&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&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&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&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",
|
||||
}
|
||||
`;
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
67
backend/src/emails/supportLine.spec.ts
Normal file
67
backend/src/emails/supportLine.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
2
backend/src/emails/templates/includes/support.pug
Normal file
2
backend/src/emails/templates/includes/support.pug
Normal file
@ -0,0 +1,2 @@
|
||||
p= t('support')
|
||||
a(href='mailto:' + supportEmail)= supportEmail
|
||||
@ -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
|
||||
|
||||
29
backend/src/graphql/queries/statistics.ts
Normal file
29
backend/src/graphql/queries/statistics.ts
Normal 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
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -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(
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
})
|
||||
})
|
||||
|
||||
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -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!
|
||||
}
|
||||
|
||||
|
||||
1329
backend/yarn.lock
1329
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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:
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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
12
package-lock.json
generated
@ -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": [
|
||||
{
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 } })),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "Необходимы ежемесячные пожертвования",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user