Compare commits

..

No commits in common. "master" and "b3.11.0-2" have entirely different histories.

2522 changed files with 71929 additions and 151273 deletions

View File

@ -1,103 +0,0 @@
# CodeRabbit Configuration
# https://docs.coderabbit.ai/guides/configure-coderabbit
language: de
reviews:
pre_merge_checks:
docstrings:
mode: "off"
# Automatisches Review für alle PRs
auto_review:
enabled: true
drafts: false
base_branches:
- master
- main
auto_approve:
enabled: true
# Review-Einstellungen
request_changes_workflow: true
high_level_summary: true
poem: false
review_status: true
collapse_walkthrough: false
changed_files_summary: true
# Pfad-Filter (Dateien die ignoriert werden)
path_filters:
- "!**/*.lock"
- "!**/package-lock.json"
- "!**/yarn.lock"
- "!**/*.snap"
- "!**/coverage/**"
- "!**/dist/**"
- "!**/node_modules/**"
- "!**/*.min.js"
- "!**/*.min.css"
- "!**/.git/**"
- "!**/storybook-static/**"
# Instruktionen für spezifische Pfade
path_instructions:
- path: "webapp/**/*.vue"
instructions: |
Prüfe Vue.js Best Practices:
- Composition API Verwendung
- Props Validierung
- Event-Handling
- Reaktivität
- path: "webapp/**/*.spec.js"
instructions: |
Prüfe Test-Qualität:
- Aussagekräftige Test-Namen
- Edge Cases abgedeckt
- Mocking korrekt verwendet
- path: "backend/**/*.js"
instructions: |
Prüfe Backend Best Practices:
- Error Handling
- Input Validierung
- SQL Injection Prevention
- Performance (N+1 Queries)
- path: "packages/ui/**/*.ts"
instructions: |
Prüfe UI Library Standards:
- TypeScript Typisierung
- Vue 2/3 Kompatibilität (vue-demi)
- Accessibility (WCAG 2.1)
- CVA Varianten-Pattern
- path: "packages/ui/**/*.vue"
instructions: |
Prüfe UI Komponenten:
- Render-Funktion Pattern für vue-demi
- Props mit korrekten Types
- Slots dokumentiert
- Keine Template-Syntax (nur h() für Vue 2/3 Kompatibilität)
# Chat-Befehle
chat:
auto_reply: true
# Ton und Stil
tone_instructions: |
Sei konstruktiv und freundlich.
Erkläre das "Warum" hinter Vorschlägen.
Priorisiere Sicherheit > Korrektheit > Performance > Lesbarkeit.
Schlage konkrete Code-Änderungen vor wenn möglich.
# Knowledge Base (Repository-spezifisches Wissen)
knowledge_base:
learnings:
scope: auto
issues:
scope: auto
pull_requests:
scope: auto

143
.github/dependabot.yml vendored
View File

@ -127,70 +127,79 @@ updates:
timezone: "Europe/Berlin" timezone: "Europe/Berlin"
time: "03:00" time: "03:00"
# ui library # frontend
- package-ecosystem: npm # - package-ecosystem: npm
open-pull-requests-limit: 99 # open-pull-requests-limit: 99
directory: "/packages/ui" # directory: "/frontend"
rebase-strategy: "disabled" # rebase-strategy: "disabled"
schedule: # schedule:
interval: weekly # interval: weekly
day: "saturday" # day: "saturday"
timezone: "Europe/Berlin" # timezone: "Europe/Berlin"
time: "03:00" # time: "03:00"
groups: # groups:
vue: # eslint:
applies-to: version-updates # applies-to: version-updates
patterns: # patterns:
- "vue*" # - "eslint*"
- "@vue*" # - "@eslint*"
vite: # pinia:
applies-to: version-updates # applies-to: version-updates
patterns: # patterns:
- "vite*" # - "pinia*"
- "@vitejs*" # react:
vitest: # applies-to: version-updates
applies-to: version-updates # patterns:
patterns: # - "react*"
- "vitest*" # remark:
- "@vitest*" # applies-to: version-updates
# patterns:
# ui examples # - "remark*"
- package-ecosystem: npm # storybook:
open-pull-requests-limit: 99 # applies-to: version-updates
directory: "/packages/ui/examples/vue3-tailwind" # patterns:
rebase-strategy: "disabled" # - "storybook"
schedule: # - "@storybook*"
interval: weekly # stylelint:
day: "saturday" # applies-to: version-updates
timezone: "Europe/Berlin" # patterns:
time: "03:00" # - "stylelint*"
# typescript:
- package-ecosystem: npm # applies-to: version-updates
open-pull-requests-limit: 99 # patterns:
directory: "/packages/ui/examples/vue3-css" # - "ts*"
rebase-strategy: "disabled" # - "@types*"
schedule: # - "typescript"
interval: weekly # vite:
day: "saturday" # applies-to: version-updates
timezone: "Europe/Berlin" # patterns:
time: "03:00" # - "vite"
# - "vite-plugin*"
- package-ecosystem: npm # - "@vitejs/plugin-vue"
open-pull-requests-limit: 99 # vitest:
directory: "/packages/ui/examples/vue2-tailwind" # applies-to: version-updates
rebase-strategy: "disabled" # patterns:
schedule: # - "vitest"
interval: weekly # - "@vitest*"
day: "saturday" # vue:
timezone: "Europe/Berlin" # applies-to: version-updates
time: "03:00" # patterns:
# - "*vue?(/)*"
- package-ecosystem: npm # exclude-patterns:
open-pull-requests-limit: 99 # - "vuetify"
directory: "/packages/ui/examples/vue2-css" # - "*vuepress*"
rebase-strategy: "disabled" # - "vue-tsc"
schedule: # vuepress:
interval: weekly # applies-to: version-updates
day: "saturday" # patterns:
timezone: "Europe/Berlin" # - "vuepress"
time: "03:00" # - "@vuepress*"
# - package-ecosystem: docker
# open-pull-requests-limit: 99
# directory: "/frontend"
# rebase-strategy: "disabled"
# schedule:
# interval: weekly
# day: "saturday"
# timezone: "Europe/Berlin"
# time: "03:00"

View File

@ -1,9 +1,5 @@
# These file filter patterns are used by the action https://github.com/dorny/paths-filter # These file filter patterns are used by the action https://github.com/dorny/paths-filter
ui: &ui
- '.github/workflows/ui-*.yml'
- 'packages/ui/**/*'
backend: &backend backend: &backend
- '.github/workflows/test-backend.yml' - '.github/workflows/test-backend.yml'
- 'backend/**/*' - 'backend/**/*'
@ -16,9 +12,7 @@ docker: &docker
webapp: &webapp webapp: &webapp
- '.github/workflows/test-webapp.yml' - '.github/workflows/test-webapp.yml'
- 'webapp/**/*' - 'webapp/**/*'
- 'styleguide/**/*'
- 'package.json' - 'package.json'
- *ui
docs-check: &docs-check docs-check: &docs-check
- '.github/workflows/check-documentation.yml' - '.github/workflows/check-documentation.yml'
@ -37,3 +31,23 @@ vuepress: &vuepress
documentation: &documentation documentation: &documentation
- *vuepress - *vuepress
- *markdown - *markdown
# frontend
frontend-test-lint-code: &frontend-test-lint-code
- 'frontend/**/*'
frontend-test-unit-code: &frontend-test-unit-code
- 'frontend/**/*'
frontend-test-build-code: &frontend-test-build-code
- 'frontend/**/*'
frontend-test-build-docker: &frontend-test-build-docker
- 'frontend/**/*'
frontend-test-build-docs: &frontend-test-build-docs
- 'frontend/**/*.md'
- 'frontend/.vuepress/*'
frontend-test-build-storybook: &frontend-test-build-storybook
- 'frontend/**/*'

View File

@ -11,7 +11,7 @@ jobs:
documentation: ${{ steps.changes.outputs.documentation }} documentation: ${{ steps.changes.outputs.documentation }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for markdown file changes - name: Check for markdown file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -28,13 +28,13 @@ jobs:
if: needs.files-changed.outputs.markdown == 'true' if: needs.files-changed.outputs.markdown == 'true'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Remove uncheckable documentation files - name: Remove uncheckable documentation files
run: rm -rf ./CHANGELOG.md # workaround until https://github.com/gaurav-nelson/github-action-markdown-link-check/pull/183 has been done run: rm -rf ./CHANGELOG.md # workaround until https://github.com/gaurav-nelson/github-action-markdown-link-check/pull/183 has been done
- name: Check Markdown Links - name: Check Markdown Links
uses: gaurav-nelson/github-action-markdown-link-check@3c3b66f1f7d0900e37b71eca45b63ea9eedfce31 # 1.0.15 uses: gaurav-nelson/github-action-markdown-link-check@1b916f2cf6c36510a6059943104e3c42ce6c16bc # 1.0.15
with: with:
use-quiet-mode: 'yes' use-quiet-mode: 'yes'
use-verbose-mode: 'no' use-verbose-mode: 'no'
@ -51,10 +51,10 @@ jobs:
if: needs.files-changed.outputs.documentation == 'true' if: needs.files-changed.outputs.documentation == 'true'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup Node 20 - name: Setup Node 20
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.0.3
with: with:
node-version: '20' node-version: '20'

View File

@ -0,0 +1,42 @@
###############################################################################
# A Github repo has max 10 GB of cache.
# https://github.blog/changelog/2021-11-23-github-actions-cache-size-is-now-increased-to-10gb-per-repository/
#
# To avoid "cache thrashing" by their cache eviction policy it is recommended
# to apply a cache cleanup workflow at PR closing to dele cache leftovers of
# the current branch:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
###############################################################################
name: ocelot.social cache cleanup on pr closing
on:
pull_request:
types:
- closed
jobs:
clean-branch-cache:
name: Cleanup branch cache
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
REPO=${{ github.repository }}
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -13,7 +13,7 @@ jobs:
documentation: ${{ steps.changes.outputs.documentation }} documentation: ${{ steps.changes.outputs.documentation }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for file changes - name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -27,10 +27,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup Node 20 - name: Setup Node 20
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.0.3
with: with:
node-version: 20 node-version: 20

View File

@ -24,27 +24,27 @@ jobs:
file: backend/Dockerfile file: backend/Dockerfile
target: production target: production
- name: webapp-base - name: webapp-base
context: . context: webapp
file: webapp/Dockerfile file: webapp/Dockerfile
target: base target: base
- name: webapp-build - name: webapp-build
context: . context: webapp
file: webapp/Dockerfile file: webapp/Dockerfile
target: build target: build
- name: webapp - name: webapp
context: . context: webapp
file: webapp/Dockerfile file: webapp/Dockerfile
target: production target: production
- name: maintenance-base - name: maintenance-base
context: . context: webapp
file: webapp/Dockerfile.maintenance file: webapp/Dockerfile.maintenance
target: base target: base
- name: maintenance-build - name: maintenance-build
context: . context: webapp
file: webapp/Dockerfile.maintenance file: webapp/Dockerfile.maintenance
target: build target: build
- name: maintenance - name: maintenance
context: . context: webapp
file: webapp/Dockerfile.maintenance file: webapp/Dockerfile.maintenance
target: production target: production
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -59,18 +59,16 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: | tags: |
@ -83,7 +81,7 @@ jobs:
type=sha type=sha
- name: Build and push Docker images - name: Build and push Docker images
id: push id: push
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with: with:
context: ${{ matrix.app.context }} context: ${{ matrix.app.context }}
target: ${{ matrix.app.target }} target: ${{ matrix.app.target }}
@ -91,5 +89,3 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.app.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.app.name }}

View File

@ -0,0 +1,37 @@
name: "frontend:test:build test code"
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - frontend-test-build-code
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.changes.outputs.frontend-test-build-code }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build:
if: needs.files-changed.outputs.changes == 'true'
name: Build - Frontend
needs: files-changed
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Frontend | Build
run: npm install && npm run build
working-directory: ${{env.WORKING_DIRECTORY}}

View File

@ -0,0 +1,52 @@
name: "frontend:test:build test docker"
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - frontend-test-build-docker
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.changes.outputs.frontend-test-build-docker }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build-production:
if: needs.files-changed.outputs.changes == 'true'
name: Build Docker Production - Frontend
needs: files-changed
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Frontend | Build Docker Production
run: docker compose -f docker-compose.yml build
working-directory: ${{env.WORKING_DIRECTORY}}
build-development:
if: needs.files-changed.outputs.changes == 'true'
name: Build Docker Development - Frontend
needs: files-changed
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Frontend | Build Docker Development
run: docker compose build
working-directory: ${{env.WORKING_DIRECTORY}}

View File

@ -0,0 +1,37 @@
name: "frontend:test:build test docs"
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - frontend-test-build-docs
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.changes.outputs.frontend-test-build-docs }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build:
if: needs.files-changed.outputs.changes == 'true'
name: Build Docs - Frontend
needs: files-changed
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Frontend | Build Docs
run: npm install && npm run docs:build
working-directory: ${{env.WORKING_DIRECTORY}}

View File

@ -0,0 +1,37 @@
name: "frontend:test:build test storybook"
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - frontend-test-build-storybook
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.changes.outputs.frontend-test-build-storybook }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
storybook:
if: needs.files-changed.outputs.changes == 'true'
name: Build Storybook - Frontend
needs: files-changed
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Frontend | Build Storybook
run: npm install && npm run storybook:build
working-directory: ${{env.WORKING_DIRECTORY}}

View File

@ -0,0 +1,37 @@
name: "frontend:test:lint code with defined linters"
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - frontend-test-lint-code
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.changes.outputs.frontend-test-lint-code }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
lint:
if: needs.files-changed.outputs.changes == 'true'
name: Lint - Frontend
needs: files-changed
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Frontend | Lint
run: npm install && npm run test:lint
working-directory: ${{env.WORKING_DIRECTORY}}

View File

@ -0,0 +1,37 @@
name: "frontend:test:unit test code with defined suites"
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - frontend-test-unit-code
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.changes.outputs.frontend-test-unit-code }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
unit:
if: needs.files-changed.outputs.changes == 'true'
name: Unit - Frontend
needs: files-changed
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: ./frontend
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Frontend | Unit
run: npm install && npm run test:unit
working-directory: ${{env.WORKING_DIRECTORY}}

View File

@ -14,13 +14,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
with: with:
fetch-depth: 0 # Fetch full History for changelog fetch-depth: 0 # Fetch full History for changelog
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
- name: Setup env - name: Setup env
run: | run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
@ -58,13 +54,9 @@ jobs:
needs: [github_tag] needs: [github_tag]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
with: with:
fetch-depth: 0 # Fetch full History for changelog fetch-depth: 0 # Fetch full History for changelog
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
- name: Setup env - name: Setup env
run: | run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
@ -72,7 +64,7 @@ jobs:
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV - run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
#- name: Repository Dispatch #- name: Repository Dispatch
# uses: peter-evans/repository-dispatch@f49a8ac5751834a0666df77deb0289abbe2b3a78 # v3.0.0 # uses: peter-evans/repository-dispatch@6846232b0e1bfd17c14dce7ac13fd3fcefe22c0c # v3.0.0
# with: # with:
# token: ${{ github.token }} # token: ${{ github.token }}
# event-type: trigger-ocelot-build-success # event-type: trigger-ocelot-build-success
@ -80,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}"}' # 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 - name: Repository Dispatch stage.ocelot.social
uses: peter-evans/repository-dispatch@f49a8ac5751834a0666df77deb0289abbe2b3a78 # v3.0.0 uses: peter-evans/repository-dispatch@6846232b0e1bfd17c14dce7ac13fd3fcefe22c0c # v3.0.0
with: with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success event-type: trigger-ocelot-build-success
@ -88,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}"}' 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 - name: Repository Dispatch stage.yunite.me
uses: peter-evans/repository-dispatch@f49a8ac5751834a0666df77deb0289abbe2b3a78 # v3.0.0 uses: peter-evans/repository-dispatch@6846232b0e1bfd17c14dce7ac13fd3fcefe22c0c # v3.0.0
with: with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success event-type: trigger-ocelot-build-success

View File

@ -11,7 +11,7 @@ jobs:
backend: ${{ steps.changes.outputs.backend }} backend: ${{ steps.changes.outputs.backend }}
docker: ${{ steps.changes.outputs.docker }} docker: ${{ steps.changes.outputs.docker }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for backend file changes - name: Check for backend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -28,28 +28,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Neo4J | Build 'community' image - name: Neo4J | Build 'community' image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 run: |
with: docker compose -f docker-compose.yml -f docker-compose.test.yml build neo4j
context: neo4j docker save "ghcr.io/ocelot-social-community/ocelot-social/neo4j:community" > /tmp/neo4j.tar
file: neo4j/Dockerfile
target: community
load: true
tags: ghcr.io/ocelot-social-community/ocelot-social/neo4j:community
cache-from: type=gha,scope=neo4j
cache-to: type=gha,mode=max,scope=neo4j
- name: Save image for test job
run: docker save "ghcr.io/ocelot-social-community/ocelot-social/neo4j:community" > /tmp/neo4j.tar
- name: Cache docker images - name: Cache docker images
id: cache-neo4j id: cache-neo4j
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/neo4j.tar path: /tmp/neo4j.tar
key: ${{ github.run_id }}-backend-neo4j-cache key: ${{ github.run_id }}-backend-neo4j-cache
@ -61,28 +49,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: backend | Build 'test' image - name: backend | Build 'test' image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 run: |
with: docker compose -f docker-compose.yml -f docker-compose.test.yml build backend
context: backend docker save "ghcr.io/ocelot-social-community/ocelot-social/backend:test" > /tmp/backend.tar
file: backend/Dockerfile
target: test
load: true
tags: ghcr.io/ocelot-social-community/ocelot-social/backend:test
cache-from: type=gha,scope=backend-test
cache-to: type=gha,mode=max,scope=backend-test
- name: Save image for test job
run: docker save "ghcr.io/ocelot-social-community/ocelot-social/backend:test" > /tmp/backend.tar
- name: Cache docker images - name: Cache docker images
id: cache-backend id: cache-backend
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/backend.tar path: /tmp/backend.tar
key: ${{ github.run_id }}-backend-cache key: ${{ github.run_id }}-backend-cache
@ -94,17 +70,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: 'backend/.nvmrc'
cache: 'yarn'
cache-dependency-path: 'backend/yarn.lock'
- name: backend | Lint - name: backend | Lint
run: cd backend && yarn --frozen-lockfile && yarn run lint run: cd backend && yarn && yarn run lint
unit_test_backend: unit_test_backend:
name: Unit tests - Backend name: Unit tests - Backend
@ -115,17 +84,17 @@ jobs:
checks: write checks: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Restore Neo4J cache - name: Restore Neo4J cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/neo4j.tar path: /tmp/neo4j.tar
key: ${{ github.run_id }}-backend-neo4j-cache key: ${{ github.run_id }}-backend-neo4j-cache
fail-on-cache-miss: true fail-on-cache-miss: true
- name: Restore Backend cache - name: Restore Backend cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/backend.tar path: /tmp/backend.tar
key: ${{ github.run_id }}-backend-cache key: ${{ github.run_id }}-backend-cache
@ -139,6 +108,7 @@ jobs:
- name: backend | copy env files - name: backend | copy env files
run: | run: |
cp webapp/.env.template webapp/.env cp webapp/.env.template webapp/.env
cp frontend/.env.dist frontend/.env
cp backend/.env.template backend/.env cp backend/.env.template backend/.env
- name: backend | docker compose - name: backend | docker compose
@ -153,3 +123,20 @@ jobs:
- name: backend | Unit test incl. coverage check - name: backend | Unit test incl. coverage check
run: docker compose exec -T backend yarn test run: docker compose exec -T backend yarn test
cleanup:
name: Cleanup
if: ${{ needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.docker == 'true' }}
needs: [files-changed, unit_test_backend]
runs-on: ubuntu-latest
permissions: write-all
continue-on-error: true
steps:
- name: Delete cache
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh extension install actions/gh-actions-cache
KEY="${{ github.run_id }}-backend-neo4j-cache"
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm
KEY="${{ github.run_id }}-backend-cache"
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm

View File

@ -8,54 +8,30 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f # v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Copy backend env file - name: Copy backend env file
run: | run: |
cp backend/.env.test_e2e backend/.env cp backend/.env.test_e2e backend/.env
cp webapp/.env.template webapp/.env cp webapp/.env.template webapp/.env
- name: Neo4J | Build image - name: Build backend and dependencies
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: neo4j
file: neo4j/Dockerfile
target: community
load: true
tags: ghcr.io/ocelot-social-community/ocelot-social/neo4j:community
cache-from: type=gha,scope=neo4j
cache-to: type=gha,mode=max,scope=neo4j
- name: Backend | Build image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: backend
file: backend/Dockerfile
target: test
load: true
tags: ghcr.io/ocelot-social-community/ocelot-social/backend:test
cache-from: type=gha,scope=backend-test
cache-to: type=gha,mode=max,scope=backend-test
- name: Pull third-party images
run: | run: |
docker pull quay.io/minio/minio:latest # Build and start all required images for backend
docker pull quay.io/minio/mc:latest docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach neo4j backend --build
docker pull maildev/maildev:latest
- name: Save all images # Save the build images
run: |
docker save "ghcr.io/ocelot-social-community/ocelot-social/backend:test" > /tmp/backend.tar docker save "ghcr.io/ocelot-social-community/ocelot-social/backend:test" > /tmp/backend.tar
docker save "ghcr.io/ocelot-social-community/ocelot-social/neo4j:community" > /tmp/neo4j.tar docker save "ghcr.io/ocelot-social-community/ocelot-social/neo4j:community" > /tmp/neo4j.tar
docker save "quay.io/minio/minio:latest" > /tmp/minio.tar docker save "quay.io/minio/minio:latest" > /tmp/minio.tar
docker save "quay.io/minio/mc:latest" > /tmp/minio-mc.tar docker save "quay.io/minio/mc:latest" > /tmp/minio-mc.tar
docker save "maildev/maildev:latest" > /tmp/mailserver.tar docker save "maildev/maildev:latest" > /tmp/mailserver.tar
# Stop the containers
docker compose -f docker-compose.yml -f docker-compose.test.yml down
- name: Cache docker images - name: Cache docker images
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: | path: |
/tmp/backend.tar /tmp/backend.tar
@ -70,27 +46,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f # v4.2.2
- name: Set up Docker Buildx - name: Copy backend env file
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 run: |
cp backend/.env.test_e2e backend/.env
cp webapp/.env.template webapp/.env
- name: Webapp | Build 'test' image - name: Build docker image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 run: |
with: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach webapp --build --no-deps
context: . docker save "ghcr.io/ocelot-social-community/ocelot-social/webapp:test" > /tmp/webapp.tar
file: webapp/Dockerfile
target: test
load: true
tags: ghcr.io/ocelot-social-community/ocelot-social/webapp:test
cache-from: type=gha,scope=webapp-test
cache-to: type=gha,mode=max,scope=webapp-test
- name: Save image for test jobs
run: docker save "ghcr.io/ocelot-social-community/ocelot-social/webapp:test" > /tmp/webapp.tar
- name: Cache docker image - name: Cache docker image
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache key: ${{ github.run_id }}-e2e-webapp-cache
@ -99,16 +68,13 @@ jobs:
name: Fullstack | prepare cypress name: Fullstack | prepare cypress
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Delete huge unnecessary tools folder
run: rm -rf /opt/hostedtoolcache
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f # v4.2.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@08f58d1471bff7f3a07d167b4ad7df25d5fcfcb6 # v4.4.0
with: with:
node-version-file: 'backend/.nvmrc' node-version-file: 'backend/.tool-versions'
cache: 'yarn' cache: 'yarn'
- name: Copy env files - name: Copy env files
@ -118,9 +84,7 @@ jobs:
- name: Install cypress requirements - name: Install cypress requirements
run: | run: |
sudo wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-amd64" wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
echo "66a2ef158866c3ecb3d8e49a7189814a485bddca43e133e4ca5735b8d3951bf7 /opt/cucumber-json-formatter" | sha256sum -c -
sudo chmod +x /opt/cucumber-json-formatter
cd backend cd backend
yarn install yarn install
yarn build yarn build
@ -129,7 +93,7 @@ jobs:
- name: Cache docker image - name: Cache docker image
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: | path: |
/opt/cucumber-json-formatter /opt/cucumber-json-formatter
@ -137,45 +101,29 @@ jobs:
/home/runner/work/Ocelot-Social/Ocelot-Social /home/runner/work/Ocelot-Social/Ocelot-Social
key: ${{ github.run_id }}-e2e-cypress key: ${{ github.run_id }}-e2e-cypress
list_features:
name: List Feature Files
runs-on: ubuntu-latest
outputs:
features: ${{ steps.list.outputs.features }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: List feature files
id: list
run: |
FEATURES=$(find cypress/e2e/ -maxdepth 1 -name "*.feature" -printf '%f\n' | sort | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "features=$FEATURES" >> $GITHUB_OUTPUT
fullstack_tests: fullstack_tests:
name: E2E | ${{ matrix.feature }} name: Fullstack | tests
if: success() if: success()
needs: [prepare_backend_environment, prepare_webapp_image, prepare_cypress, list_features] needs: [prepare_backend_environment, prepare_webapp_image, prepare_cypress]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
jobs: 8
strategy: strategy:
fail-fast: false
matrix: matrix:
feature: ${{ fromJson(needs.list_features.outputs.features) }} # run copies of the current job in parallel
job: [1, 2, 3, 4, 5, 6, 7, 8]
steps: steps:
- name: Delete huge unnecessary tools folder
run: rm -rf /opt/hostedtoolcache
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f # v4.2.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@08f58d1471bff7f3a07d167b4ad7df25d5fcfcb6 # v4.4.0
with: with:
node-version-file: 'backend/.nvmrc' node-version-file: 'backend/.tool-versions'
cache: 'yarn' cache: 'yarn'
- name: Restore cypress cache - name: Restore cypress cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: | path: |
/opt/cucumber-json-formatter /opt/cucumber-json-formatter
@ -185,7 +133,7 @@ jobs:
restore-keys: ${{ github.run_id }}-e2e-cypress restore-keys: ${{ github.run_id }}-e2e-cypress
- name: Restore backend environment cache - name: Restore backend environment cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: | path: |
/tmp/backend.tar /tmp/backend.tar
@ -196,27 +144,15 @@ jobs:
key: ${{ github.run_id }}-e2e-backend-environment-cache key: ${{ github.run_id }}-e2e-backend-environment-cache
- name: Restore webapp cache - name: Restore webapp cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache key: ${{ github.run_id }}-e2e-webapp-cache
- name: Copy env files
run: |
cp webapp/.env.template webapp/.env
cp backend/.env.test_e2e backend/.env
- name: Ensure cucumber-json-formatter exists
run: |
if [ ! -f /opt/cucumber-json-formatter ]; then
sudo wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-amd64"
echo "66a2ef158866c3ecb3d8e49a7189814a485bddca43e133e4ca5735b8d3951bf7 /opt/cucumber-json-formatter" | sha256sum -c -
fi
sudo chmod +x /opt/cucumber-json-formatter
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
- name: Boot up test system | docker compose - name: Boot up test system | docker compose
run: | run: |
chmod +x /opt/cucumber-json-formatter
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
docker load < /tmp/neo4j.tar docker load < /tmp/neo4j.tar
docker load < /tmp/backend.tar docker load < /tmp/backend.tar
docker load < /tmp/minio.tar docker load < /tmp/minio.tar
@ -224,18 +160,11 @@ jobs:
docker load < /tmp/mailserver.tar docker load < /tmp/mailserver.tar
docker load < /tmp/webapp.tar docker load < /tmp/webapp.tar
docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach backend mailserver webapp docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach backend mailserver webapp
sleep 90s
echo "Waiting for backend (max 120s)..."
timeout 120 bash -c 'until curl -sf -X POST -H "Content-Type: application/json" -d "{\"query\":\"{__typename}\"}" http://localhost:4000 > /dev/null 2>&1; do sleep 5; done'
echo "Backend is ready."
echo "Waiting for webapp (max 120s)..."
timeout 120 bash -c 'until curl -sf http://localhost:3000 > /dev/null 2>&1; do sleep 5; done'
echo "Webapp is ready."
- name: Full stack tests | run tests - name: Full stack tests | run tests
id: e2e-tests id: e2e-tests
run: yarn run cypress:run --spec "cypress/e2e/${{ matrix.feature }}" run: yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }} )
- name: Full stack tests | if tests failed, compile html report - name: Full stack tests | if tests failed, compile html report
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
@ -246,21 +175,29 @@ jobs:
- name: Full stack tests | if tests failed, upload report - name: Full stack tests | if tests failed, upload report
id: e2e-report id: e2e-report
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: e2e-report-${{ matrix.feature }} name: ocelot-e2e-test-report-pr${{ needs.docker_preparation.outputs.pr-number }}
path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report
e2e_status: cleanup_cache:
name: E2E | Status name: Cleanup Cache
if: always() needs: fullstack_tests
needs: [fullstack_tests]
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true
steps: steps:
- name: Check E2E results - name: Checkout code
run: | uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f # v4.2.2
if [ "${{ needs.fullstack_tests.result }}" != "success" ]; then
echo "E2E tests failed or were cancelled (result: ${{ needs.fullstack_tests.result }})"
exit 1
fi
- name: Full stack tests | cleanup cache
run: |
cacheKeys=$(gh cache list --json key --jq '.[] | select(.key | startswith("${{ github.run_id }}-e2e-")) | .key')
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeys
do
gh cache delete "$cacheKey"
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -11,7 +11,7 @@ jobs:
docker: ${{ steps.changes.outputs.docker }} docker: ${{ steps.changes.outputs.docker }}
webapp: ${{ steps.changes.outputs.webapp }} webapp: ${{ steps.changes.outputs.webapp }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Check for frontend file changes - name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -28,12 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: 'webapp/.nvmrc'
- name: Check translation files - name: Check translation files
run: | run: |
@ -43,31 +38,19 @@ jobs:
build_test_webapp: build_test_webapp:
name: Docker Build Test - Webapp name: Docker Build Test - Webapp
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true' if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true'
needs: files-changed needs: [files-changed, prepare]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Webapp | Build 'test' image - name: Webapp | Build 'test' image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 run: |
with: docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/
context: . docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar
file: webapp/Dockerfile
target: test
load: true
tags: ghcr.io/ocelot-social-community/ocelot-social/webapp:test
cache-from: type=gha,scope=webapp-test
cache-to: type=gha,mode=max,scope=webapp-test
- name: Save image for test job
run: docker save "ghcr.io/ocelot-social-community/ocelot-social/webapp:test" > /tmp/webapp.tar
- name: Cache docker image - name: Cache docker image
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-webapp-cache key: ${{ github.run_id }}-webapp-cache
@ -79,17 +62,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: 'webapp/.nvmrc'
cache: 'yarn'
cache-dependency-path: 'webapp/yarn.lock'
- name: webapp | Lint - name: webapp | Lint
run: cd webapp && yarn --frozen-lockfile && yarn run lint run: cd webapp && yarn && yarn run lint
unit_test_webapp: unit_test_webapp:
name: Unit Tests - Webapp name: Unit Tests - Webapp
@ -100,10 +76,10 @@ jobs:
checks: write checks: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Restore webapp cache - name: Restore webapp cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2 uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-webapp-cache key: ${{ github.run_id }}-webapp-cache
@ -114,10 +90,29 @@ jobs:
- name: Copy env files - name: Copy env files
run: | run: |
cp webapp/.env.template webapp/.env cp webapp/.env.template webapp/.env
cp frontend/.env.dist frontend/.env
cp backend/.env.template backend/.env cp backend/.env.template backend/.env
- name: Start webapp container - name: backend | docker compose
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp # doesn't work without the --build flag - this either means we should not load the cached images or cache the correct image
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp --build
- name: webapp | Unit tests incl. coverage check - name: webapp | Unit tests incl. coverage check
run: docker compose exec -T webapp yarn test run: docker compose exec -T webapp yarn test
cleanup:
name: Cleanup
if: ${{ needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true' }}
needs: [files-changed, unit_test_webapp]
runs-on: ubuntu-latest
permissions: write-all
continue-on-error: true
steps:
- name: Delete cache
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh extension install actions/gh-actions-cache
KEY="${{ github.run_id }}-webapp-cache"
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }} if: ${{ github.actor != 'dependabot[bot]' }}
steps: steps:
- uses: amannn/action-semantic-pull-request@069817c298f23fab00a8f29a2e556a5eac0f6390 # v5.5.3 - uses: amannn/action-semantic-pull-request@335288255954904a41ddda8947c8f2c844b8bfeb # v5.5.3
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@ -29,11 +29,10 @@ jobs:
# Configure which scopes are allowed (newline delimited). # Configure which scopes are allowed (newline delimited).
scopes: | scopes: |
backend backend
package/ui
webapp webapp
frontend
maintenance maintenance
database database
e2e
docu docu
docker docker
release release

View File

@ -1,95 +0,0 @@
name: UI Build
on:
push:
branches: [master]
pull_request:
branches: [master]
defaults:
run:
working-directory: packages/ui
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
build:
name: Build
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build library
run: npm run build
- name: Verify build output
run: |
echo "Checking build output..."
# Check that dist directory exists
if [ ! -d "dist" ]; then
echo "::error::dist directory not found"
exit 1
fi
# Check required files exist
FILES=(
"dist/index.mjs"
"dist/index.cjs"
"dist/index.d.ts"
"dist/index.d.cts"
"dist/tailwind.preset.mjs"
"dist/tailwind.preset.cjs"
"dist/tailwind.preset.d.ts"
"dist/tailwind.preset.d.cts"
"dist/style.css"
)
for file in "${FILES[@]}"; do
if [ ! -f "$file" ]; then
echo "::error::Missing required file: $file"
exit 1
fi
echo "✓ $file"
done
echo ""
echo "All build outputs verified!"
- name: Validate package
run: npm run validate
- name: Upload build artifacts
uses: actions/upload-artifact@v6
with:
name: dist
path: packages/ui/dist/
retention-days: 7

View File

@ -1,121 +0,0 @@
name: UI Compatibility
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
build-library:
name: Build Library
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/ui
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build library
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v6
with:
name: ui-dist
path: packages/ui/dist/
retention-days: 1
test-compatibility:
name: Test Compatibility
if: needs.files-changed.outputs.ui == 'true'
needs: [files-changed, build-library]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
example:
- vue3-tailwind
- vue3-css
- vue2-tailwind
- vue2-css
defaults:
run:
working-directory: packages/ui/examples/${{ matrix.example }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: ui-dist
path: packages/ui/dist/
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/examples/${{ matrix.example }}/package-lock.json
- name: Install dependencies
run: npm install
- name: Lint
run: npm run lint
- name: Run tests
run: npm test
- name: Build example app
run: npm run build
compatibility-result:
name: Compatibility Result
if: always()
needs: [files-changed, test-compatibility]
runs-on: ubuntu-latest
steps:
- name: Skip if no UI changes
if: needs.files-changed.outputs.ui != 'true'
run: echo "No UI changes detected, skipping."
- name: Check matrix results
if: needs.files-changed.outputs.ui == 'true'
run: |
if [ "${{ needs.test-compatibility.result }}" != "success" ]; then
echo "Compatibility tests failed"
exit 1
fi

View File

@ -1,59 +0,0 @@
name: UI Docker
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
build:
name: Build Docker Image
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build development image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: ./packages/ui
file: ./packages/ui/Dockerfile
target: development
push: false
tags: ocelot-social/ui:development
cache-from: type=gha,scope=ui-development
cache-to: type=gha,mode=max,scope=ui-development
- name: Build production image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: ./packages/ui
file: ./packages/ui/Dockerfile
target: production
push: false
tags: ocelot-social/ui:latest
cache-from: type=gha,scope=ui-production
cache-to: type=gha,mode=max,scope=ui-production

View File

@ -1,54 +0,0 @@
name: UI Lint
on:
push:
branches: [master]
pull_request:
branches: [master]
defaults:
run:
working-directory: packages/ui
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
lint:
name: ESLint
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run TypeScript type check
run: npm run typecheck

View File

@ -1,65 +0,0 @@
name: UI Release
on:
push:
branches: [master]
paths:
- 'packages/ui/**'
- 'release-please-config.json'
- '.release-please-manifest.json'
permissions:
contents: write
pull-requests: write
jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs['packages/ui--release_created'] }}
tag_name: ${{ steps.release.outputs['packages/ui--tag_name'] }}
version: ${{ steps.release.outputs['packages/ui--version'] }}
steps:
- name: Release Please
id: release
uses: googleapis/release-please-action@v4
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
publish:
name: Publish to npm
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/ui
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Validate package
run: npm run validate
- name: Publish to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -1,54 +0,0 @@
name: UI Size
on:
push:
branches: [master]
pull_request:
branches: [master]
defaults:
run:
working-directory: packages/ui
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
size:
name: Bundle Size Check
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Check bundle size
run: npm run size

View File

@ -1,74 +0,0 @@
name: UI Storybook
on:
push:
branches: [master]
pull_request:
branches: [master]
defaults:
run:
working-directory: packages/ui
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
build:
name: Build Storybook
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build Storybook
run: npm run storybook:build
- name: Verify build output
run: |
echo "Checking Storybook build output..."
if [ ! -d "storybook-static" ]; then
echo "::error::storybook-static directory not found"
exit 1
fi
if [ ! -f "storybook-static/index.html" ]; then
echo "::error::index.html not found in storybook-static"
exit 1
fi
echo "✓ Storybook build verified!"
- name: Upload Storybook artifacts
uses: actions/upload-artifact@v6
with:
name: storybook-static
path: packages/ui/storybook-static/
retention-days: 7

View File

@ -1,59 +0,0 @@
name: UI Test
on:
push:
branches: [master]
pull_request:
branches: [master]
defaults:
run:
working-directory: packages/ui
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
test:
name: Unit Tests
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage report
uses: actions/upload-artifact@v6
if: always()
with:
name: coverage-report
path: packages/ui/coverage/
retention-days: 7

View File

@ -1,51 +0,0 @@
name: UI Verify
on:
push:
branches: [master]
pull_request:
branches: [master]
defaults:
run:
working-directory: packages/ui
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
verify:
name: Completeness Check
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Check component completeness
run: npm run verify

View File

@ -1,64 +0,0 @@
name: UI Visual
on:
push:
branches: [master]
pull_request:
branches: [master]
defaults:
run:
working-directory: packages/ui
jobs:
files-changed:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
ui: ${{ steps.changes.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
visual:
name: Visual Regression
if: needs.files-changed.outputs.ui == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'packages/ui/.tool-versions'
cache: 'npm'
cache-dependency-path: packages/ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install chromium --with-deps
- name: Run visual tests
run: npm run test:visual
- name: Upload test results
uses: actions/upload-artifact@v6
if: failure()
with:
name: visual-test-results
path: |
packages/ui/test-results/
packages/ui/playwright-report/
retention-days: 7

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "styleguide"]
path = styleguide
url = https://github.com/Human-Connection/Nitro-Styleguide.git
[submodule "deployment/configurations/stage.ocelot.social"] [submodule "deployment/configurations/stage.ocelot.social"]
path = deployment/configurations/stage.ocelot.social path = deployment/configurations/stage.ocelot.social
url = git@github.com:Ocelot-Social-Community/stage.ocelot.social.git url = git@github.com:Ocelot-Social-Community/stage.ocelot.social.git

2
.nvmrc
View File

@ -1 +1 @@
v25.3.0 v24.2.0

View File

@ -1,3 +0,0 @@
{
"packages/ui": "0.0.1"
}

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
nodejs 20.12.1

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ SMTP_PASSWORD=
SMTP_SECURE="false" # true for 465, false for other ports SMTP_SECURE="false" # true for 465, false for other ports
SMTP_DKIM_DOMAINNAME= SMTP_DKIM_DOMAINNAME=
SMTP_DKIM_KEYSELECTOR= SMTP_DKIM_KEYSELECTOR=
SMTP_DKIM_PRIVATEKEY= SMTP_DKIM_PRIVATKEY=
# E-Mail settings for our 'docker compose up mailserver' # E-Mail settings for our 'docker compose up mailserver'
# SMTP_HOST=localhost # SMTP_HOST=localhost
# SMTP_PORT=1025 # SMTP_PORT=1025
@ -33,6 +33,8 @@ JWT_SECRET="b/&&7b78BF&fv/Vd"
JWT_EXPIRES="2y" JWT_EXPIRES="2y"
MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g" MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g"
PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
SENTRY_DSN_BACKEND= SENTRY_DSN_BACKEND=
COMMIT= COMMIT=
PUBLIC_REGISTRATION=false PUBLIC_REGISTRATION=false
@ -43,9 +45,7 @@ AWS_SECRET_ACCESS_KEY=12341234
AWS_ENDPOINT=http://localhost:9000 AWS_ENDPOINT=http://localhost:9000
AWS_REGION=local AWS_REGION=local
AWS_BUCKET=ocelot AWS_BUCKET=ocelot
IMAGOR_PUBLIC_URL=http://localhost:8000 S3_PUBLIC_GATEWAY=http://localhost:8000
IMAGOR_SECRET=mysecret
CATEGORIES_ACTIVE=false CATEGORIES_ACTIVE=false
MAX_PINNED_POSTS=1 MAX_PINNED_POSTS=1
MAX_GROUP_PINNED_POSTS=1

View File

@ -19,12 +19,14 @@ SMTP_PASSWORD=
SMTP_SECURE="false" # true for 465, false for other ports SMTP_SECURE="false" # true for 465, false for other ports
SMTP_DKIM_DOMAINNAME= SMTP_DKIM_DOMAINNAME=
SMTP_DKIM_KEYSELECTOR= SMTP_DKIM_KEYSELECTOR=
SMTP_DKIM_PRIVATEKEY= SMTP_DKIM_PRIVATKEY=
JWT_SECRET="b/&&7b78BF&fv/Vd" JWT_SECRET="b/&&7b78BF&fv/Vd"
JWT_EXPIRES="2y" JWT_EXPIRES="2y"
MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g" MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g"
PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
SENTRY_DSN_BACKEND= SENTRY_DSN_BACKEND=
COMMIT= COMMIT=
PUBLIC_REGISTRATION=false PUBLIC_REGISTRATION=false
@ -35,9 +37,7 @@ AWS_SECRET_ACCESS_KEY=12341234
AWS_ENDPOINT=http://localhost:9000 AWS_ENDPOINT=http://localhost:9000
AWS_REGION=local AWS_REGION=local
AWS_BUCKET=ocelot AWS_BUCKET=ocelot
IMAGOR_PUBLIC_URL=http://localhost:8000 S3_PUBLIC_GATEWAY=http://localhost:8000
IMAGOR_SECRET=mysecret
CATEGORIES_ACTIVE=false CATEGORIES_ACTIVE=false
MAX_PINNED_POSTS=1 MAX_PINNED_POSTS=1
MAX_GROUP_PINNED_POSTS=1

3
backend/.eslintignore Normal file
View File

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

232
backend/.eslintrc.cjs Normal file
View File

@ -0,0 +1,232 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
root: true,
env: {
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint', 'import', 'n', 'promise', 'security', 'no-catch-all'],
extends: [
'standard',
'eslint:recommended',
'plugin:n/recommended',
'plugin:prettier/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:promise/recommended',
'plugin:security/recommended-legacy',
'plugin:@eslint-community/eslint-comments/recommended',
'prettier',
],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: ['./tsconfig.json', './backend/tsconfig.json'],
},
node: true,
},
},
rules: {
'no-catch-all/no-catch-all': 'error',
'no-console': 'error',
camelcase: 'error',
'no-debugger': 'error',
'prettier/prettier': [
'error',
{
htmlWhitespaceSensitivity: 'ignore',
},
],
// import
'import/export': 'error',
// 'import/no-deprecated': 'error',
'import/no-empty-named-blocks': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-unused-modules': 'error',
'import/no-named-as-default': 'error',
'import/no-named-as-default-member': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-nodejs-modules': 'off',
'import/unambiguous': 'off', // not compatible with .eslintrc.cjs
'import/default': 'error',
'import/named': 'off', // has false positives
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
'import/no-self-import': 'error',
'import/no-unresolved': 'error',
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/consistent-type-specifier-style': 'error',
'import/exports-last': 'off',
'import/extensions': 'error',
'import/first': 'error',
'import/group-exports': 'off',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'off', // not compatible with neode
'import/no-default-export': 'off', // not compatible with neode
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
'import/no-unassigned-import': 'error',
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
pathGroups: [
{
pattern: '@?*/**',
group: 'external',
position: 'after',
},
{
pattern: '@/**',
group: 'external',
position: 'after',
},
],
alphabetize: {
order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
caseInsensitive: true /* ignore case. Options: [true, false] */,
},
distinctGroup: true,
},
],
'import/prefer-default-export': 'off',
// n
// 'n/callback-return': 'error',
'n/exports-style': 'error',
'n/file-extension-in-import': ['error', 'never'],
'n/global-require': 'error',
'n/handle-callback-err': 'error',
// 'n/hashbang': 'error', // part of n/recommended
'n/no-callback-literal': 'error',
// 'n/no-deprecated-api': 'error', // part of n/recommended
// 'n/no-exports-assign': 'error', // part of n/recommended
'n/no-extraneous-import': 'off', // duplicate of import/no-extraneous-dependencies // part of n/recommended
// 'n/no-extraneous-require': 'error', // part of n/recommended
'n/no-hide-core-modules': 'error',
'n/no-missing-import': 'off', // not compatible with typescript // part of n/recommended
// 'n/no-missing-require': 'error', // part of n/recommended
'n/no-mixed-requires': 'error',
'n/no-new-require': 'error',
'n/no-path-concat': 'error',
'n/no-process-env': 'error',
// 'n/no-process-exit': 'error', // part of n/recommended
'n/no-restricted-import': 'error',
'n/no-restricted-require': 'error',
'n/no-sync': 'error',
// 'n/no-unpublished-bin': 'error', // part of n/recommended
'n/no-unpublished-import': [
'error',
{ allowModules: ['apollo-server-testing', 'rosie', '@faker-js/faker', 'ts-jest'] },
], // part of n/recommended
'n/no-unpublished-require': ['error', { allowModules: ['ts-jest', 'require-json5'] }], // part of n/recommended
// 'n/no-unsupported-features/es-builtins': 'error', // part of n/recommended
// 'n/no-unsupported-features/es-syntax': 'error', // part of n/recommended
// 'n/no-unsupported-features/node-builtins': 'error', // part of n/recommended
'n/prefer-global/buffer': 'error',
'n/prefer-global/console': 'error',
'n/prefer-global/process': 'error',
'n/prefer-global/text-decoder': 'error',
'n/prefer-global/text-encoder': 'error',
'n/prefer-global/url': 'error',
'n/prefer-global/url-search-params': 'error',
'n/prefer-node-protocol': 'error',
'n/prefer-promises/dns': 'error',
'n/prefer-promises/fs': 'error',
// 'n/process-exit-as-throw': 'error', // part of n/recommended
'n/shebang': 'error',
// promise
// 'promise/always-return': 'error', // part of promise/recommended
'promise/avoid-new': 'error',
// 'promise/catch-or-return': 'error', // part of promise/recommended
// 'promise/no-callback-in-promise': 'warn', // part of promise/recommended
'promise/no-multiple-resolved': 'error',
'promise/no-native': 'off', // ES5 only
// 'promise/no-nesting': 'warn', // part of promise/recommended
// 'promise/no-new-statics': 'error', // part of promise/recommended
// 'promise/no-promise-in-callback': 'warn', // part of promise/recommended
// 'promise/no-return-in-finally': 'warn', // part of promise/recommended
// 'promise/no-return-wrap': 'error', // part of promise/recommended
// 'promise/param-names': 'error', // part of promise/recommended
'promise/prefer-await-to-callbacks': 'error',
'promise/prefer-catch': 'error',
'promise/spec-only': 'error',
// 'promise/valid-params': 'error', // part of promise/recommended
// eslint comments
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
'@eslint-community/eslint-comments/no-restricted-disable': 'error',
'@eslint-community/eslint-comments/no-use': 'off',
'@eslint-community/eslint-comments/require-description': 'off',
},
overrides: [
// only for ts files
{
files: ['*.ts', '*.tsx'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
'prettier',
],
rules: {
// allow explicitly defined dangling promises
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
// ignore prefer-regexp-exec rule to allow string.match(regex)
'@typescript-eslint/prefer-regexp-exec': 'off',
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
'import/unambiguous': 'off',
// this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable
'@typescript-eslint/no-unnecessary-condition': 'off',
// respect underscore as acceptable unused variable
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},
},
{
files: ['*.spec.ts'],
plugins: ['jest'],
env: {
jest: true,
},
rules: {
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'error',
'jest/valid-expect': 'error',
'@typescript-eslint/unbound-method': 'off',
'jest/unbound-method': 'error',
},
},
{
extends: ['plugin:jsonc/recommended-with-jsonc'],
files: ['*.json', '*.json5', '*.jsonc'],
parser: 'jsonc-eslint-parser',
},
],
}

View File

@ -1 +1 @@
v25.3.0 v24.2.0

9
backend/.prettierrc.cjs Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
semi: false,
printWidth: 100,
singleQuote: true,
trailingComma: "all",
tabWidth: 2,
bracketSpacing: true
};

1
backend/.tool-versions Normal file
View File

@ -0,0 +1 @@
nodejs 24.2.0

View File

@ -1,5 +1,4 @@
# syntax=docker/dockerfile:1 FROM node:24.4.0-alpine AS base
FROM node:25.6.1-alpine AS base
LABEL org.label-schema.name="ocelot.social:backend" 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.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" LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md"
@ -29,15 +28,13 @@ ONBUILD COPY ./branding/email/ src/middleware/helpers/email/
ONBUILD COPY ./branding/middlewares/ src/middleware/branding/ ONBUILD COPY ./branding/middlewares/ src/middleware/branding/
ONBUILD COPY ./branding/data/ src/db/data ONBUILD COPY ./branding/data/ src/db/data
ONBUILD COPY ./branding/public/ public/ ONBUILD COPY ./branding/public/ public/
ONBUILD RUN --mount=type=cache,target=/yarn-cache,sharing=locked \ ONBUILD RUN yarn install --production=false --frozen-lockfile --non-interactive
yarn install --production=false --frozen-lockfile --non-interactive --cache-folder /yarn-cache
ONBUILD RUN yarn run build ONBUILD RUN yarn run build
ONBUILD RUN mkdir /build ONBUILD RUN mkdir /build
ONBUILD RUN cp -r ./build /build ONBUILD RUN cp -r ./build /build
ONBUILD RUN cp -r ./public /build ONBUILD RUN cp -r ./public /build
ONBUILD RUN cp -r ./package.json yarn.lock /build ONBUILD RUN cp -r ./package.json yarn.lock /build
ONBUILD RUN --mount=type=cache,target=/yarn-cache,sharing=locked \ ONBUILD RUN cd /build && yarn install --production=true --frozen-lockfile --non-interactive
cd /build && yarn install --production=true --frozen-lockfile --non-interactive --cache-folder /yarn-cache
FROM build AS test FROM build AS test
# required for the migrations # required for the migrations

View File

@ -19,16 +19,18 @@ Wait a little until your backend is up and running at [http://localhost:4000/](h
## Installation without Docker ## Installation without Docker
For the local installation you need a recent version of For the local installation you need a recent version of
[Node](https://nodejs.org/en/). We are using [Node](https://nodejs.org/en/) (&gt;= `v16.19.0`). We are using
`v25.3.0` and therefore we recommend to use the same version. You can use the `v24.2.0` and therefore we recommend to use the same version
([see](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4082)
some known problems with more recent node versions). You can use the
[node version manager](https://github.com/nvm-sh/nvm) `nvm` to switch [node version manager](https://github.com/nvm-sh/nvm) `nvm` to switch
between different local Node versions: between different local Node versions:
```sh ```sh
# install Node using '.nvmrc' file # install Node
$ cd backend $ cd backend
$ nvm install $ nvm install v24.2.0
$ nvm use $ nvm use v24.2.0
``` ```
Install node dependencies with [yarn](https://yarnpkg.com/en/): Install node dependencies with [yarn](https://yarnpkg.com/en/):

View File

@ -1,89 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import config from 'eslint-config-it4c'
import graphql from 'eslint-config-it4c/modules/graphql'
import jest from 'eslint-config-it4c/modules/jest'
export default [
{
ignores: ['node_modules/', 'build/', 'coverage/'],
},
...config,
...jest,
// GraphQL schema linting (extend file pattern to include .gql)
...graphql.map((c) => ({
...c,
files: ['**/*.graphql', '**/*.gql'],
})),
{
files: ['**/*.graphql', '**/*.gql'],
// TODO: Parser must be set explicitly because the it4c module only provides
// plugins and rules, not languageOptions. Without this, ESLint uses the JS
// parser for .gql files. Remove when fixed in eslint-config-it4c.
languageOptions: {
parser: graphql[0].plugins['@graphql-eslint'].parser,
parserOptions: {
graphQLConfig: {
schema: './src/graphql/types/**/*.gql',
documents: './src/graphql/queries/**/*.gql',
},
},
},
rules: {
// Would require descriptions on every type/field/input — too noisy for now
'@graphql-eslint/require-description': 'off',
// camelCase operation names and _id/_ne underscores conflict with existing schema
'@graphql-eslint/naming-convention': 'off',
// Many types (Image, File, InviteCode, etc.) intentionally lack id: ID!
'@graphql-eslint/strict-id-in-types': 'off',
// Fields like groupType, queryLocations match parent type name by coincidence
'@graphql-eslint/no-typename-prefix': 'off',
// neo4j-graphql-js adds arguments (first, offset) at runtime not present in static schema
'@graphql-eslint/known-argument-names': 'off',
// TODO: operations-recommended rules must be disabled because the it4c
// graphql module bundles both schema and operations configs together.
// Remove when eslint-config-it4c exports them separately (e.g. graphql/schema).
'@graphql-eslint/executable-definitions': 'off',
// neo4j-graphql-js adds fields at runtime (_id, relations) not present in static schema
'@graphql-eslint/fields-on-correct-type': 'off',
},
},
{
// Backend-specific TypeScript overrides
files: ['**/*.ts'],
languageOptions: {
parserOptions: {
projectService: {
allowDefaultProject: ['eslint.config.ts', 'jest.config.ts', 'prettier.config.ts'],
},
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// TypeORM compatibility: joined tables can be null but are not defined as nullable
'@typescript-eslint/no-unnecessary-condition': 'off',
// Allow string.match(regex) instead of regex.exec(string)
'@typescript-eslint/prefer-regexp-exec': 'off',
// TODO: gradually add return types to exported functions, then remove this override
'@typescript-eslint/explicit-module-boundary-types': 'off',
// Allow @/* path aliases in relative parent imports
'import-x/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
},
},
{
// Jest test file overrides
files: ['**/*.spec.ts'],
rules: {
'@typescript-eslint/unbound-method': 'off',
},
},
{
// Config files: allow require() of devDependencies
files: ['*.config.{js,mjs,cjs,ts,mts,cts}'],
rules: {
'n/no-unpublished-require': 'off',
},
},
]

27
backend/jest.config.cjs Normal file
View File

@ -0,0 +1,27 @@
/* eslint-disable import/no-commonjs */
const requireJSON5 = require('require-json5')
const { pathsToModuleNameMapper } = require('ts-jest')
const { compilerOptions } = requireJSON5('./tsconfig.json')
module.exports = {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverageFrom: [
'**/*.ts',
'!**/node_modules/**',
'!**/test/**',
'!**/build/**',
'!**/src/**/?(*.)+(spec|test).ts?(x)',
'!**/src/db/**',
],
coverageThreshold: {
global: {
lines: 90,
},
},
testMatch: ['**/src/**/?(*.)+(spec|test).ts?(x)'],
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
}

View File

@ -1,40 +0,0 @@
import { readFileSync } from 'node:fs'
import { pathsToModuleNameMapper } from 'ts-jest'
import { parseConfigFileTextToJson } from 'typescript'
// eslint-disable-next-line n/no-sync -- config files are synchronous by nature
const tsconfigText = readFileSync('./tsconfig.json', 'utf-8')
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- parseConfigFileTextToJson returns untyped config
const { config } = parseConfigFileTextToJson('tsconfig.json', tsconfigText)
const paths = (config as { compilerOptions: { paths: Record<string, string[]> } }).compilerOptions
.paths
export default {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverageFrom: [
'**/*.ts',
'!**/node_modules/**',
'!**/test/**',
'!**/build/**',
'!**/src/**/?(*.)+(spec|test).ts?(x)',
'!**/src/db/**',
'!*.config.ts',
'!**/*.d.ts',
'!**/gql-register.ts',
],
coverageThreshold: {
global: {
lines: 93,
},
},
testMatch: ['**/src/**/?(*.)+(spec|test).ts?(x)'],
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
transform: {
'\\.gql$': '<rootDir>/test/graphqlTransform.ts',
'\\.tsx?$': 'ts-jest',
},
moduleNameMapper: pathsToModuleNameMapper(paths, { prefix: '<rootDir>/' }),
}

View File

@ -1,6 +1,6 @@
{ {
"name": "ocelot-social-backend", "name": "ocelot-social-backend",
"version": "3.14.1", "version": "3.11.0",
"description": "GraphQL Backend for ocelot.social", "description": "GraphQL Backend for ocelot.social",
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
"author": "ocelot.social Community", "author": "ocelot.social Community",
@ -10,111 +10,122 @@
"scripts": { "scripts": {
"start": "node build/src/", "start": "node build/src/",
"build": "tsc && tsc-alias && ./scripts/build.copy.files.sh", "build": "tsc && tsc-alias && ./scripts/build.copy.files.sh",
"dev": "nodemon --exec tsx src/index.ts -e js,ts,gql", "dev": "nodemon --exec ts-node --require tsconfig-paths/register src/index.ts -e js,ts,gql",
"dev:debug": "nodemon --exec node --inspect=0.0.0.0:9229 build/src/index.js -e js,ts,gql", "dev:debug": "nodemon --exec node --inspect=0.0.0.0:9229 build/src/index.js -e js,ts,gql",
"lint": "eslint --max-warnings 0 .", "lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts,.cjs,.json,.json5,.jsonc .",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles", "test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
"db:reset": "tsx src/db/reset.ts", "db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts",
"db:reset:withmigrations": "tsx src/db/reset-with-migrations.ts", "db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts",
"db:seed": "tsx --require ./src/graphql/gql-register.ts src/db/seed.ts", "db:seed": "ts-node --require tsconfig-paths/register src/db/seed.ts",
"db:data:admin": "tsx src/db/admin.ts", "db:data:admin": "ts-node --require tsconfig-paths/register src/db/admin.ts",
"db:data:badges": "tsx src/db/badges.ts", "db:data:badges": "ts-node --require tsconfig-paths/register src/db/badges.ts",
"db:data:branding": "tsx src/db/data-branding.ts", "db:data:branding": "ts-node --require tsconfig-paths/register src/db/data-branding.ts",
"db:data:categories": "tsx src/db/categories.ts", "db:data:categories": "ts-node --require tsconfig-paths/register src/db/categories.ts",
"db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts", "db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts",
"db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create", "db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create",
"db:func:disable:notifications": "tsx src/db/disable-notifications.ts",
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js", "prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js",
"prod:db:data:branding": "node build/src/db/data-branding.js", "prod:db:data:branding": "node build/src/db/data-branding.js",
"prod:db:data:categories": "node build/src/db/categories.js", "prod:db:data:categories": "node build/src/db/categories.js"
"prod:db:data:admin": "node build/src/db/admin.js",
"prod:db:func:disable:notifications": "node build/src/db/disable-notifications.js"
}, },
"dependencies": { "dependencies": {
"@apollo/server": "^4.11.3", "@aws-sdk/client-s3": "^3.844.0",
"@aws-sdk/client-s3": "^3.1000.0", "@aws-sdk/lib-storage": "^3.842.0",
"@aws-sdk/lib-storage": "^3.1000.0", "@sentry/node": "^5.15.4",
"@graphql-tools/load-files": "^7.0.0",
"@graphql-tools/merge": "^9.0.0",
"@sentry/node": "^5.30.0",
"@types/mime-types": "^3.0.1", "@types/mime-types": "^3.0.1",
"bcryptjs": "~3.0.3", "apollo-server": "~2.14.2",
"body-parser": "^2.2.2", "apollo-server-express": "^2.14.2",
"cheerio": "~1.2.0", "bcryptjs": "~3.0.2",
"cross-env": "~10.1.0", "body-parser": "^1.20.3",
"cheerio": "~1.1.0",
"cross-env": "~7.0.3",
"dotenv": "~17.0.1", "dotenv": "~17.0.1",
"email-templates": "^13.0.1", "email-templates": "^12.0.3",
"express": "^4.22.1", "express": "^5.1.0",
"graphql": "^16.13.0", "graphql": "^14.6.0",
"graphql-middleware": "~6.1.35", "graphql-middleware": "~4.0.2",
"graphql-middleware-sentry": "^3.2.1",
"graphql-redis-subscriptions": "^2.7.0", "graphql-redis-subscriptions": "^2.7.0",
"graphql-shield": "^7.6.5", "graphql-shield": "~7.2.2",
"graphql-subscriptions": "^2.0.0", "graphql-subscriptions": "^1.1.0",
"graphql-tag": "~2.10.3",
"graphql-upload": "^13.0.0", "graphql-upload": "^13.0.0",
"graphql-ws": "^5.16.2",
"helmet": "~8.1.0", "helmet": "~8.1.0",
"ioredis": "^5.9.3", "ioredis": "^5.6.1",
"jsonwebtoken": "~8.5.1", "jsonwebtoken": "~8.5.1",
"languagedetect": "^2.0.0", "languagedetect": "^2.0.0",
"linkify-html": "^4.3.2", "linkify-html": "^4.3.1",
"linkifyjs": "^4.3.2", "linkifyjs": "^4.3.1",
"lodash": "~4.17.23", "lodash": "~4.17.21",
"metascraper": "^5.49.24", "merge-graphql-schemas": "^1.7.8",
"metascraper-author": "^5.49.24", "metascraper": "^5.49.1",
"metascraper-date": "^5.49.24", "metascraper-author": "^5.49.1",
"metascraper-description": "^5.49.24", "metascraper-date": "^5.49.1",
"metascraper-image": "^5.49.24", "metascraper-description": "^5.49.1",
"metascraper-lang": "^5.49.24", "metascraper-image": "^5.49.1",
"metascraper-lang": "^5.49.1",
"metascraper-lang-detector": "^4.10.2", "metascraper-lang-detector": "^4.10.2",
"metascraper-logo": "^5.49.24", "metascraper-logo": "^5.49.1",
"metascraper-publisher": "^5.49.24", "metascraper-publisher": "^5.49.1",
"metascraper-soundcloud": "^5.34.4", "metascraper-soundcloud": "^5.34.4",
"metascraper-title": "^5.49.24", "metascraper-title": "^5.49.1",
"metascraper-url": "^5.49.24", "metascraper-url": "^5.49.1",
"metascraper-video": "^5.49.24", "metascraper-video": "^5.49.1",
"metascraper-youtube": "^5.49.24", "metascraper-youtube": "^5.49.1",
"migrate": "^2.1.0", "migrate": "^2.1.0",
"mime-types": "^3.0.2", "mime-types": "^3.0.1",
"minimatch": "^10.2.4", "minimatch": "^10.0.3",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"neo4j-driver": "^4.4.11", "neo4j-driver": "^4.4.11",
"neo4j-graphql-js": "2.11.5", "neo4j-graphql-js": "^2.11.5",
"neode": "^0.4.9", "neode": "^0.4.9",
"node-fetch": "^2.7.0", "node-fetch": "^2.7.0",
"nodemailer": "^8.0.1", "nodemailer": "^7.0.5",
"nodemailer-html-to-text": "^3.2.0", "nodemailer-html-to-text": "^3.2.0",
"preview-email": "^3.1.1", "preview-email": "^3.1.0",
"pug": "^3.0.3", "pug": "^3.0.3",
"sanitize-html": "~2.17.1", "sanitize-html": "~2.17.0",
"slugify": "^1.6.6", "slug": "~9.1.0",
"subscriptions-transport-ws": "^0.11.0",
"trunc-html": "~1.1.2", "trunc-html": "~1.1.2",
"tslog": "^4.10.2", "tslog": "^4.9.3",
"uuid": "~9.0.1", "uuid": "~9.0.1",
"validator": "^13.15.26", "validator": "^13.15.15",
"ws": "^8.18.2",
"xregexp": "^5.1.2" "xregexp": "^5.1.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
"@faker-js/faker": "9.9.0", "@faker-js/faker": "9.9.0",
"@types/email-templates": "^10.0.4", "@types/email-templates": "^10.0.4",
"@types/jest": "^30.0.0", "@types/jest": "^29.5.14",
"@types/jsonwebtoken": "~8.5.1", "@types/jsonwebtoken": "~8.5.1",
"@types/lodash": "^4.17.24", "@types/lodash": "^4.17.20",
"@types/node": "^25.3.2", "@types/node": "^24.0.14",
"@types/request": "^2.48.13", "@types/request": "^2.48.12",
"@types/slug": "^5.0.9", "@types/slug": "^5.0.9",
"@types/uuid": "~9.0.1", "@types/uuid": "~9.0.1",
"@types/ws": "^8.18.1", "@typescript-eslint/eslint-plugin": "^5.62.0",
"eslint": "^9.27.0", "@typescript-eslint/parser": "^5.62.0",
"eslint-config-it4c": "^0.12.0", "apollo-server-testing": "~2.11.0",
"jest": "^30.2.0", "eslint": "^8.57.1",
"nodemon": "~3.1.14", "eslint-config-prettier": "^10.1.5",
"prettier": "^3.8.1", "eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-jsonc": "^2.20.1",
"eslint-plugin-n": "^17.21.0",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-security": "^3.0.1",
"jest": "^29.7.0",
"nodemon": "~3.1.10",
"prettier": "^3.6.2",
"require-json5": "^1.3.0",
"rosie": "^2.1.1", "rosie": "^2.1.1",
"ts-jest": "^29.4.6", "ts-jest": "^29.4.0",
"ts-node": "^10.9.2",
"tsc-alias": "^1.8.16", "tsc-alias": "^1.8.16",
"tsx": "^4.21.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"resolutions": { "resolutions": {
@ -122,11 +133,7 @@
"**/graphql-upload": "^11.0.0", "**/graphql-upload": "^11.0.0",
"**/strip-ansi": "6.0.1", "**/strip-ansi": "6.0.1",
"**/string-width": "4.2.0", "**/string-width": "4.2.0",
"**/wrap-ansi": "7.0.0", "**/wrap-ansi": "7.0.0"
"**/jwa": "^2.0.1",
"**/@types/express": "4.17.25",
"neo4j-graphql-js/graphql": "^16.11.0",
"graphql-upload/graphql": "^16.11.0"
}, },
"engines": { "engines": {
"node": ">=20.12.1" "node": ">=20.12.1"

View File

@ -1 +0,0 @@
export { default } from 'eslint-config-it4c/prettier'

View File

@ -1,17 +1,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable import-x/no-namespace */
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable n/no-process-env */ /* eslint-disable n/no-process-env */
import { config } from 'dotenv' import { config } from 'dotenv'
// eslint-disable-next-line import/no-namespace
import * as SMTPTransport from 'nodemailer/lib/smtp-pool'
import emails from './emails' import emails from './emails'
import metadata from './metadata' import metadata from './metadata'
import type * as SMTPTransport from 'nodemailer/lib/smtp-pool'
// Load env file // Load env file
config() config()
@ -33,7 +30,6 @@ const environment = {
: [], : [],
SEND_MAIL: env.NODE_ENV !== 'test', SEND_MAIL: env.NODE_ENV !== 'test',
LOG_LEVEL: 'DEBUG', LOG_LEVEL: 'DEBUG',
PROXY_S3: env.PROXY_S3,
} }
const server = { const server = {
@ -51,7 +47,7 @@ const SMTP_PASSWORD = env.SMTP_PASSWORD
const SMTP_DKIM_DOMAINNAME = env.SMTP_DKIM_DOMAINNAME const SMTP_DKIM_DOMAINNAME = env.SMTP_DKIM_DOMAINNAME
const SMTP_DKIM_KEYSELECTOR = env.SMTP_DKIM_KEYSELECTOR const SMTP_DKIM_KEYSELECTOR = env.SMTP_DKIM_KEYSELECTOR
// PEM format = https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html // PEM format = https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html
const SMTP_DKIM_PRIVATEKEY = env.SMTP_DKIM_PRIVATEKEY?.replace(/\\n/g, '\n') // replace all "\n" in .env string by real line break const SMTP_DKIM_PRIVATKEY = env.SMTP_DKIM_PRIVATKEY?.replace(/\\n/g, '\n') // replace all "\n" in .env string by real line break
const SMTP_MAX_CONNECTIONS = (env.SMTP_MAX_CONNECTIONS && parseInt(env.SMTP_MAX_CONNECTIONS)) || 5 const SMTP_MAX_CONNECTIONS = (env.SMTP_MAX_CONNECTIONS && parseInt(env.SMTP_MAX_CONNECTIONS)) || 5
const SMTP_MAX_MESSAGES = (env.SMTP_MAX_MESSAGES && parseInt(env.SMTP_MAX_MESSAGES)) || 100 const SMTP_MAX_MESSAGES = (env.SMTP_MAX_MESSAGES && parseInt(env.SMTP_MAX_MESSAGES)) || 100
@ -70,11 +66,11 @@ if (SMTP_USERNAME && SMTP_PASSWORD) {
pass: SMTP_PASSWORD, pass: SMTP_PASSWORD,
} }
} }
if (SMTP_DKIM_DOMAINNAME && SMTP_DKIM_KEYSELECTOR && SMTP_DKIM_PRIVATEKEY) { if (SMTP_DKIM_DOMAINNAME && SMTP_DKIM_KEYSELECTOR && SMTP_DKIM_PRIVATKEY) {
nodemailerTransportOptions.dkim = { nodemailerTransportOptions.dkim = {
domainName: SMTP_DKIM_DOMAINNAME, domainName: SMTP_DKIM_DOMAINNAME,
keySelector: SMTP_DKIM_KEYSELECTOR, keySelector: SMTP_DKIM_KEYSELECTOR,
privateKey: SMTP_DKIM_PRIVATEKEY, privateKey: SMTP_DKIM_PRIVATKEY,
} }
} }
@ -96,21 +92,19 @@ const redis = {
} }
const required = { const required = {
EMAIL_DEFAULT_SENDER: env.EMAIL_DEFAULT_SENDER,
AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY, AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY,
AWS_ENDPOINT: env.AWS_ENDPOINT, AWS_ENDPOINT: env.AWS_ENDPOINT,
AWS_REGION: env.AWS_REGION, AWS_REGION: env.AWS_REGION,
AWS_BUCKET: env.AWS_BUCKET, AWS_BUCKET: env.AWS_BUCKET,
IMAGOR_PUBLIC_URL: env.IMAGOR_PUBLIC_URL,
IMAGOR_SECRET: env.IMAGOR_SECRET,
MAPBOX_TOKEN: env.MAPBOX_TOKEN, MAPBOX_TOKEN: env.MAPBOX_TOKEN,
JWT_SECRET: env.JWT_SECRET, JWT_SECRET: env.JWT_SECRET,
PRIVATE_KEY_PASSPHRASE: env.PRIVATE_KEY_PASSPHRASE,
} }
const S3_PUBLIC_GATEWAY = env.S3_PUBLIC_GATEWAY
// https://stackoverflow.com/a/53050575 // https://stackoverflow.com/a/53050575
type NoUndefinedField<T> = { [P in keyof T]-?: NoUndefinedField<NonNullable<T[P]>> } type NoUndefinedField<T> = { [P in keyof T]-?: NoUndefinedField<NonNullable<T[P]>> }
@ -127,6 +121,7 @@ function assertRequiredConfig(
assertRequiredConfig(required) assertRequiredConfig(required)
const options = { const options = {
EMAIL_DEFAULT_SENDER: env.EMAIL_DEFAULT_SENDER,
SUPPORT_EMAIL: env.SUPPORT_EMAIL, SUPPORT_EMAIL: env.SUPPORT_EMAIL,
SUPPORT_URL: emails.SUPPORT_LINK, SUPPORT_URL: emails.SUPPORT_LINK,
APPLICATION_NAME: metadata.APPLICATION_NAME, APPLICATION_NAME: metadata.APPLICATION_NAME,
@ -141,9 +136,6 @@ const options = {
MAX_PINNED_POSTS: Number.isNaN(Number(process.env.MAX_PINNED_POSTS)) MAX_PINNED_POSTS: Number.isNaN(Number(process.env.MAX_PINNED_POSTS))
? 1 ? 1
: Number(process.env.MAX_PINNED_POSTS), : Number(process.env.MAX_PINNED_POSTS),
MAX_GROUP_PINNED_POSTS: Number.isNaN(Number(process.env.MAX_GROUP_PINNED_POSTS))
? 1
: Number(process.env.MAX_GROUP_PINNED_POSTS),
} }
const language = { const language = {
@ -159,6 +151,7 @@ const CONFIG = {
...redis, ...redis,
...options, ...options,
...language, ...language,
S3_PUBLIC_GATEWAY,
} }
export type Config = typeof CONFIG export type Config = typeof CONFIG
@ -169,8 +162,7 @@ export type S3Config = Pick<
| 'AWS_ENDPOINT' | 'AWS_ENDPOINT'
| 'AWS_REGION' | 'AWS_REGION'
| 'AWS_BUCKET' | 'AWS_BUCKET'
| 'IMAGOR_SECRET' | 'S3_PUBLIC_GATEWAY'
| 'IMAGOR_PUBLIC_URL'
> >
export default CONFIG export default CONFIG

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-shadow */
import { getDriver, getNeode } from '@db/neo4j' import { getDriver, getNeode } from '@db/neo4j'
import type { Driver } from 'neo4j-driver' import type { Driver } from 'neo4j-driver'

View File

@ -1,10 +1,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import databaseContext from '@context/database' import databaseContext from '@context/database'
import pubsubContext from '@context/pubsub' import pubsubContext from '@context/pubsub'
import CONFIG from '@src/config' import CONFIG from '@src/config'
import type { DecodedUser } from '@src/jwt/decode'
import { decode } from '@src/jwt/decode' import { decode } from '@src/jwt/decode'
import ocelotLogger from '@src/logger' import ocelotLogger from '@src/logger'
import type OcelotLogger from '@src/logger'
import type { DecodedUser } from '@src/jwt/decode' import type { ApolloServerExpressConfig } from 'apollo-server-express'
const serverDatabase = databaseContext() const serverDatabase = databaseContext()
const serverPubsub = pubsubContext() const serverPubsub = pubsubContext()
@ -14,14 +18,14 @@ export const getContext =
database?: ReturnType<typeof databaseContext> database?: ReturnType<typeof databaseContext>
pubsub?: ReturnType<typeof pubsubContext> pubsub?: ReturnType<typeof pubsubContext>
authenticatedUser: DecodedUser | null | undefined authenticatedUser: DecodedUser | null | undefined
logger?: typeof ocelotLogger logger?: typeof OcelotLogger
config: typeof CONFIG config: typeof CONFIG
}) => }) =>
async (req: { headers: { authorization?: string } }) => { async (req: { headers: { authorization?: string } }) => {
const { const {
database = serverDatabase, database = serverDatabase,
pubsub = serverPubsub, pubsub = serverPubsub,
authenticatedUser, authenticatedUser = undefined,
logger = ocelotLogger, logger = ocelotLogger,
config = CONFIG, config = CONFIG,
} = opts ?? {} } = opts ?? {}
@ -40,11 +44,18 @@ export const getContext =
req, req,
cypherParams: { cypherParams: {
currentUserId: user ? user.id : null, currentUserId: user ? user.id : null,
languageDefault: config.LANGUAGE_DEFAULT.toUpperCase(),
}, },
config, config,
} }
return result return result
} }
export const context: ApolloServerExpressConfig['context'] = async (options) => {
const { connection, req } = options
if (connection) {
return connection.context
} else {
return getContext()(req)
}
}
export type Context = Awaited<ReturnType<ReturnType<typeof getContext>>> export type Context = Awaited<ReturnType<ReturnType<typeof getContext>>>

View File

@ -2,7 +2,7 @@
import { getNeode } from './neo4j' import { getNeode } from './neo4j'
import { trophies, verification } from './seed/badges' import { trophies, verification } from './seed/badges'
// eslint-disable-next-line import-x/newline-after-import // eslint-disable-next-line import/newline-after-import
;(async function () { ;(async function () {
const neode = getNeode() const neode = getNeode()
try { try {

View File

@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable import-x/no-commonjs */ /* eslint-disable import/no-commonjs */
// eslint-disable-next-line n/no-unpublished-require // eslint-disable-next-line n/no-unpublished-require, @typescript-eslint/no-var-requires
const tsx = require('tsx/cjs/api') const tsNode = require('ts-node')
// eslint-disable-next-line import/no-unassigned-import, n/no-unpublished-require
require('tsconfig-paths/register')
module.exports = tsx.register module.exports = tsNode.register

View File

@ -2,7 +2,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/await-thenable */
import { readdir } from 'node:fs/promises' import { readdir } from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'

View File

@ -1,61 +0,0 @@
import databaseContext from '@context/database'
const run = async () => {
const args = process.argv.slice(2)
if (args.length !== 1) {
// eslint-disable-next-line no-console
console.error('Usage: yarn run db:func:disable-notifications <email>')
process.exit(1)
}
const email = args[0]
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
// eslint-disable-next-line no-console
console.error('Error: Invalid email address format')
process.exit(1)
}
const { write } = databaseContext()
const result = (
await write({
query: `
MATCH (:EmailAddress {email: $email})-[:BELONGS_TO]->(user:User)
SET user.emailNotificationsFollowingUsers = false
SET user.emailNotificationsPostInGroup = false
SET user.emailNotificationsCommentOnObservedPost = false
SET user.emailNotificationsMention = false
SET user.emailNotificationsChatMessage = false
SET user.emailNotificationsGroupMemberJoined = false
SET user.emailNotificationsGroupMemberLeft = false
SET user.emailNotificationsGroupMemberRemoved = false
SET user.emailNotificationsGroupMemberRoleChanged = false
RETURN toString(count(user)) as count
`,
variables: {
email,
},
})
).records[0].get('count') as string
if (result !== '1') {
// eslint-disable-next-line no-console
console.error(`User with email address ${email} not found`)
process.exit(1)
}
// eslint-disable-next-line no-console
console.log(`Notifications for User with email address ${email} disabled`)
process.exit(0)
}
void (async function () {
await run()
})()

View File

@ -4,22 +4,19 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable n/no-unpublished-import */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { faker } from '@faker-js/faker' import { faker } from '@faker-js/faker'
import { hashSync } from 'bcryptjs' import { hashSync } from 'bcryptjs'
import { Factory } from 'rosie' import { Factory } from 'rosie'
import slugify from 'slugify' import slugify from 'slug'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { generateInviteCode } from '@graphql/resolvers/inviteCodes' import { generateInviteCode } from '@graphql/resolvers/inviteCodes'
import { isUniqueFor } from '@middleware/sluggifyMiddleware' import { isUniqueFor } from '@middleware/sluggifyMiddleware'
import uniqueSlug from '@middleware/slugify/uniqueSlug' import uniqueSlug from '@middleware/slugify/uniqueSlug'
import { Context } from '@src/context'
import { getDriver, getNeode } from './neo4j' import { getDriver, getNeode } from './neo4j'
import type { Context } from '@src/context'
const neode = getNeode() const neode = getNeode()
const uniqueImageUrl = (imageUrl) => { const uniqueImageUrl = (imageUrl) => {
@ -52,14 +49,14 @@ Factory.define('category')
.attr('id', uuid) .attr('id', uuid)
.attr('icon', 'globe') .attr('icon', 'globe')
.attr('name', 'Global Peace & Nonviolence') .attr('name', 'Global Peace & Nonviolence')
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('Category', buildObject) return neode.create('Category', buildObject)
}) })
Factory.define('badge') Factory.define('badge')
.attr('type', 'crowdfunding') .attr('type', 'crowdfunding')
.attr('status', 'permanent') .attr('status', 'permanent')
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('Badge', buildObject) return neode.create('Badge', buildObject)
}) })
@ -70,7 +67,7 @@ Factory.define('image')
.attr('alt', faker.lorem.sentence) .attr('alt', faker.lorem.sentence)
.attr('type', 'image/jpeg') .attr('type', 'image/jpeg')
.attr('url', null) .attr('url', null)
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
if (!buildObject.url) { if (!buildObject.url) {
buildObject.url = faker.image.urlPicsumPhotos({ buildObject.url = faker.image.urlPicsumPhotos({
width: buildObject.width, width: buildObject.width,
@ -87,7 +84,7 @@ Factory.define('file')
.attr('name', faker.lorem.slug) .attr('name', faker.lorem.slug)
.attr('type', 'image/jpeg') .attr('type', 'image/jpeg')
.attr('url', null) .attr('url', null)
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
if (!buildObject.url) { if (!buildObject.url) {
buildObject.url = faker.image.urlPicsumPhotos() buildObject.url = faker.image.urlPicsumPhotos()
} }
@ -174,8 +171,8 @@ Factory.define('post')
return Promise.all([Factory.build('category')]) return Promise.all([Factory.build('category')])
}) */ }) */
.option('tagIds', []) .option('tagIds', [])
.option('tags', ['tagIds'], async (tagIds) => { .option('tags', ['tagIds'], (tagIds) => {
return Promise.all(tagIds.map(async (id) => neode.find('Tag', id))) return Promise.all(tagIds.map((id) => neode.find('Tag', id)))
}) })
.option('authorId', null) .option('authorId', null)
.option('author', ['authorId'], (authorId) => { .option('author', ['authorId'], (authorId) => {
@ -267,7 +264,7 @@ Factory.define('donations')
.attr('showDonations', true) .attr('showDonations', true)
.attr('goal', 15000) .attr('goal', 15000)
.attr('progress', 7000) .attr('progress', 7000)
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('Donations', buildObject) return neode.create('Donations', buildObject)
}) })
@ -278,13 +275,13 @@ const emailDefaults = {
Factory.define('emailAddress') Factory.define('emailAddress')
.attrs(emailDefaults) .attrs(emailDefaults)
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('EmailAddress', buildObject) return neode.create('EmailAddress', buildObject)
}) })
Factory.define('unverifiedEmailAddress') Factory.define('unverifiedEmailAddress')
.attr(emailDefaults) .attr(emailDefaults)
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('UnverifiedEmailAddress', buildObject) return neode.create('UnverifiedEmailAddress', buildObject)
}) })
@ -297,7 +294,7 @@ const inviteCodeDefaults = {
Factory.define('inviteCode') Factory.define('inviteCode')
.attrs(inviteCodeDefaults) .attrs(inviteCodeDefaults)
.option('groupId', null) .option('groupId', null)
.option('group', ['groupId'], async (groupId) => { .option('group', ['groupId'], (groupId) => {
if (groupId) { if (groupId) {
return neode.find('Group', groupId) return neode.find('Group', groupId)
} }
@ -334,11 +331,11 @@ Factory.define('location')
id: 'country.10743216036480410', id: 'country.10743216036480410',
type: 'country', type: 'country',
}) })
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('Location', buildObject) return neode.create('Location', buildObject)
}) })
Factory.define('report').after(async (buildObject, _options) => { Factory.define('report').after((buildObject, _options) => {
return neode.create('Report', buildObject) return neode.create('Report', buildObject)
}) })
@ -346,7 +343,7 @@ Factory.define('tag')
.attrs({ .attrs({
name: '#human-connection', name: '#human-connection',
}) })
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('Tag', buildObject) return neode.create('Tag', buildObject)
}) })
@ -354,7 +351,7 @@ Factory.define('socialMedia')
.attrs({ .attrs({
url: 'https://mastodon.social/@Gargron', url: 'https://mastodon.social/@Gargron',
}) })
.after(async (buildObject, _options) => { .after((buildObject, _options) => {
return neode.create('SocialMedia', buildObject) return neode.create('SocialMedia', buildObject)
}) })

View File

@ -2,7 +2,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
import { getDriver, getNeode } from '@db/neo4j' import { getDriver, getNeode } from '@db/neo4j'
class Store { class Store {
@ -84,7 +83,7 @@ class Store {
const driver = getDriver() const driver = getDriver()
const session = driver.session() const session = driver.session()
const { migrations } = set const { migrations } = set
const writeTxResultPromise = session.writeTransaction(async (txc) => { const writeTxResultPromise = session.writeTransaction((txc) => {
return Promise.all( return Promise.all(
migrations.map(async (migration) => { migrations.map(async (migration) => {
const { title, description, timestamp } = migration const { title, description, timestamp } = migration

View File

@ -4,9 +4,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable import-x/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable promise/prefer-await-to-callbacks */
/* eslint-disable import-x/no-deprecated */
import { throwError, concat } from 'rxjs' import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators' import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
@ -72,7 +71,7 @@ export function up(next) {
), ),
) )
.subscribe({ .subscribe({
next: ({ user, email, _oldUser, oldEmail }) => { next: ({ user, email, _oldUser, oldEmail }) =>
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(` console.log(`
Merged: Merged:
@ -80,8 +79,7 @@ export function up(next) {
userId: ${user.id} userId: ${user.id}
email: ${oldEmail} => ${email.email} email: ${oldEmail} => ${email.email}
============================= =============================
`) `),
},
complete: () => { complete: () => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('Merging of duplicate users completed') console.log('Merging of duplicate users completed')

View File

@ -4,9 +4,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable import-x/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable promise/prefer-await-to-callbacks */
/* eslint-disable import-x/no-deprecated */
import { throwError, concat } from 'rxjs' import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError } from 'rxjs/operators' import { flatMap, mergeMap, map, catchError } from 'rxjs/operators'
@ -66,7 +65,7 @@ export function up(next) {
), ),
) )
.subscribe({ .subscribe({
next: ({ updatedLocation, location }) => { next: ({ updatedLocation, location }) =>
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(` console.log(`
Merged: Merged:
@ -74,8 +73,7 @@ export function up(next) {
locationId: ${location.id} locationId: ${location.id}
updatedLocation: ${location.id} => ${updatedLocation.id} updatedLocation: ${location.id} => ${updatedLocation.id}
============================= =============================
`) `),
},
complete: () => { complete: () => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('Merging of duplicate locations completed') console.log('Merging of duplicate locations completed')

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-empty-function */
'use strict' 'use strict'
export async function up(_next) {} export async function up(_next) {}

View File

@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/await-thenable */
import { open } from 'node:fs/promises' import { open } from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
@ -38,7 +37,9 @@ export async function up(_next) {
urls = urls.filter((url) => url.startsWith('/uploads')) urls = urls.filter((url) => url.startsWith('/uploads'))
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('URLS uploaded:') console.log('URLS uploaded:')
for await (const url of urls) { await Promise.all(
urls
.map((url) => async () => {
const { pathname } = new URL(url, 'http://example.org') const { pathname } = new URL(url, 'http://example.org')
// TODO: find a better way to do this - this is quite a hack // TODO: find a better way to do this - this is quite a hack
const fileLocation = const fileLocation =
@ -68,8 +69,10 @@ export async function up(_next) {
const [updatedUrl] = updatedRecord.records.map((record) => record.get('url') as string) const [updatedUrl] = updatedRecord.records.map((record) => record.get('url') as string)
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(updatedUrl) console.log(updatedUrl)
// return updatedUrl return updatedUrl
} })
.map((p) => p()),
)
await transaction.commit() await transaction.commit()
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -1,51 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { getDriver } from '@db/neo4j'
export const description = 'Delete follow relation for deleted users'
export async function up(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// Implement your migration here.
await transaction.run(`
MATCH (:User {deleted: true})-[follow:FOLLOWS]-(:User)
DELETE follow;
`)
await transaction.commit()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
await session.close()
}
}
export async function down(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// cannot be rolled back
// Implement your migration here.
// await transaction.run(``)
// await transaction.commit()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
await session.close()
}
}

View File

@ -13,7 +13,6 @@ export default {
nameNL: { type: 'string' }, nameNL: { type: 'string' },
namePL: { type: 'string' }, namePL: { type: 'string' },
nameRU: { type: 'string' }, nameRU: { type: 'string' },
nameSQ: { type: 'string' },
isIn: { isIn: {
type: 'relationship', type: 'relationship',
relationship: 'IS_IN', relationship: 'IS_IN',

View File

@ -18,7 +18,7 @@ export default {
}, },
title: { type: 'string', disallow: [null], min: 3 }, title: { type: 'string', disallow: [null], min: 3 },
slug: { type: 'string', allow: [null], unique: 'true' }, slug: { type: 'string', allow: [null], unique: 'true' },
content: { type: 'string', disallow: [null], required: true, min: 3 }, content: { type: 'string', disallow: [null], min: 3 },
contentExcerpt: { type: 'string', allow: [null] }, contentExcerpt: { type: 'string', allow: [null] },
deleted: { type: 'boolean', default: false }, deleted: { type: 'boolean', default: false },
disabled: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false },
@ -58,7 +58,6 @@ export default {
}, },
}, },
pinned: { type: 'boolean', default: null, valid: [null, true] }, pinned: { type: 'boolean', default: null, valid: [null, true] },
groupPinned: { type: 'boolean', default: null, valid: [null, true] },
postType: { type: 'string', default: 'Article', valid: ['Article', 'Event'] }, postType: { type: 'string', default: 'Article', valid: ['Article', 'Event'] },
observes: { observes: {
type: 'relationship', type: 'relationship',

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable jest/valid-title */
import { cleanDatabase } from '@db/factories' import { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j' import { getNeode, getDriver } from '@db/neo4j'
@ -66,8 +65,8 @@ describe('slug', () => {
}) })
describe('characters', () => { describe('characters', () => {
const createUser = async (attrs) => { const createUser = (attrs) => {
return neode.create('User', attrs).then(async (user) => user.toJson()) return neode.create('User', attrs).then((user) => user.toJson())
} }
it('-', async () => { it('-', async () => {

View File

@ -1,49 +1,36 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable n/no-missing-require */
/* eslint-disable n/global-require */
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm // NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
// module that is not browser-compatible. Node's `fs` module is server-side only // module that is not browser-compatible. Node's `fs` module is server-side only
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// We use static imports instead of dynamic require() to ensure compatibility declare let Cypress: any | undefined
// with both Node.js and Webpack (used by Cypress cucumber preprocessor).
import Badge from './Badge'
import Category from './Category'
import Comment from './Comment'
import Donations from './Donations'
import EmailAddress from './EmailAddress'
import File from './File'
import Group from './Group'
import Image from './Image'
import InviteCode from './InviteCode'
import Location from './Location'
import Migration from './Migration'
import Post from './Post'
import Report from './Report'
import SocialMedia from './SocialMedia'
import Tag from './Tag'
import UnverifiedEmailAddress from './UnverifiedEmailAddress'
import User from './User'
import type Neode from 'neode'
// Type assertion needed because TypeScript infers literal types from the model
// objects (e.g., type: 'string' as literal), but Neode expects the broader
// SchemaObject type with PropertyTypes union. The Neode type definitions are
// incomplete/incorrect, so we use double assertion to bypass the check.
export default { export default {
Badge, File: typeof Cypress !== 'undefined' ? require('./File') : require('./File').default,
Category, Image: typeof Cypress !== 'undefined' ? require('./Image') : require('./Image').default,
Comment, Badge: typeof Cypress !== 'undefined' ? require('./Badge') : require('./Badge').default,
Donations, User: typeof Cypress !== 'undefined' ? require('./User') : require('./User').default,
EmailAddress, Group: typeof Cypress !== 'undefined' ? require('./Group') : require('./Group').default,
File, EmailAddress:
Group, typeof Cypress !== 'undefined' ? require('./EmailAddress') : require('./EmailAddress').default,
Image, UnverifiedEmailAddress:
InviteCode, typeof Cypress !== 'undefined'
Location, ? require('./UnverifiedEmailAddress')
Migration, : require('./UnverifiedEmailAddress').default,
Post, SocialMedia:
Report, typeof Cypress !== 'undefined' ? require('./SocialMedia') : require('./SocialMedia').default,
SocialMedia, Post: typeof Cypress !== 'undefined' ? require('./Post') : require('./Post').default,
Tag, Comment: typeof Cypress !== 'undefined' ? require('./Comment') : require('./Comment').default,
UnverifiedEmailAddress, Category: typeof Cypress !== 'undefined' ? require('./Category') : require('./Category').default,
User, Tag: typeof Cypress !== 'undefined' ? require('./Tag') : require('./Tag').default,
} as unknown as Record<string, Neode.SchemaObject> Location: typeof Cypress !== 'undefined' ? require('./Location') : require('./Location').default,
Donations:
typeof Cypress !== 'undefined' ? require('./Donations') : require('./Donations').default,
Report: typeof Cypress !== 'undefined' ? require('./Report') : require('./Report').default,
Migration:
typeof Cypress !== 'undefined' ? require('./Migration') : require('./Migration').default,
InviteCode:
typeof Cypress !== 'undefined' ? require('./InviteCode') : require('./InviteCode').default,
}

View File

@ -1,12 +1,10 @@
/* eslint-disable import-x/no-named-as-default-member */ /* eslint-disable import/no-named-as-default-member */
import neo4j from 'neo4j-driver' import neo4j, { Driver } from 'neo4j-driver'
import Neode from 'neode' import Neode from 'neode'
import CONFIG from '@config/index' import CONFIG from '@config/index'
import models from '@db/models/index' import models from '@db/models/index'
import type { Driver } from 'neo4j-driver'
let driver: Driver let driver: Driver
const defaultOptions = { const defaultOptions = {
uri: CONFIG.NEO4J_URI, uri: CONFIG.NEO4J_URI,

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable n/no-process-exit */
import CONFIG from '@config/index' import CONFIG from '@config/index'
import { cleanDatabase } from './factories' import { cleanDatabase } from './factories'

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable n/no-process-exit */
import CONFIG from '@config/index' import CONFIG from '@config/index'
import { cleanDatabase } from './factories' import { cleanDatabase } from './factories'

View File

@ -2,22 +2,19 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable n/no-unpublished-import */ /* eslint-disable n/no-process-exit */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
import { faker } from '@faker-js/faker' import { faker } from '@faker-js/faker'
import sample from 'lodash/sample' import sample from 'lodash/sample'
import CONFIG from '@config/index' import CONFIG from '@config/index'
import { categories } from '@constants/categories' import { categories } from '@constants/categories'
import CreateComment from '@graphql/queries/comments/CreateComment.gql' import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
import ChangeGroupMemberRole from '@graphql/queries/groups/ChangeGroupMemberRole.gql' import { createCommentMutation } from '@graphql/queries/createCommentMutation'
import CreateGroup from '@graphql/queries/groups/CreateGroup.gql' import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import JoinGroup from '@graphql/queries/groups/JoinGroup.gql' import { CreateMessage } from '@graphql/queries/CreateMessage'
import CreateMessage from '@graphql/queries/messaging/CreateMessage.gql' import { createPostMutation } from '@graphql/queries/createPostMutation'
import CreateRoom from '@graphql/queries/messaging/CreateRoom.gql' import { createRoomMutation } from '@graphql/queries/createRoomMutation'
import CreatePost from '@graphql/queries/posts/CreatePost.gql' import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
import Factory from './factories' import Factory from './factories'
@ -42,7 +39,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser, authenticatedUser,
config: CONFIG, config: CONFIG,
}) })
const apolloSetup = await createApolloTestSetup({ context }) const apolloSetup = createApolloTestSetup({ context })
const { mutate, server, database } = apolloSetup const { mutate, server, database } = apolloSetup
const { neode } = database const { neode } = database
@ -344,7 +341,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
console.log('seed', 'groups') console.log('seed', 'groups')
authenticatedUser = await peterLustig.toJson() authenticatedUser = await peterLustig.toJson()
await mutate({ await mutate({
mutation: CreateGroup, mutation: createGroupMutation(),
variables: { variables: {
id: 'g0', id: 'g0',
name: 'Investigative Journalism', name: 'Investigative Journalism',
@ -357,21 +354,21 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g0', groupId: 'g0',
userId: 'u2', userId: 'u2',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g0', groupId: 'g0',
userId: 'u4', userId: 'u4',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g0', groupId: 'g0',
userId: 'u6', userId: 'u6',
@ -379,7 +376,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g0', groupId: 'g0',
userId: 'u2', userId: 'u2',
@ -388,7 +385,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g0', groupId: 'g0',
userId: 'u4', userId: 'u4',
@ -399,7 +396,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('seed', 'group posts') console.log('seed', 'group posts')
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p0-g0', id: 'p0-g0',
groupId: 'g0', groupId: 'g0',
@ -411,7 +408,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await bobDerBaumeister.toJson() authenticatedUser = await bobDerBaumeister.toJson()
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p1-g0', id: 'p1-g0',
groupId: 'g0', groupId: 'g0',
@ -423,7 +420,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await jennyRostock.toJson() authenticatedUser = await jennyRostock.toJson()
await mutate({ await mutate({
mutation: CreateGroup, mutation: createGroupMutation(),
variables: { variables: {
id: 'g1', id: 'g1',
name: 'School For Citizens', name: 'School For Citizens',
@ -436,35 +433,35 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u1', userId: 'u1',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u2', userId: 'u2',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u5', userId: 'u5',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u6', userId: 'u6',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u7', userId: 'u7',
@ -472,7 +469,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u1', userId: 'u1',
@ -480,7 +477,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u5', userId: 'u5',
@ -488,7 +485,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g1', groupId: 'g1',
userId: 'u6', userId: 'u6',
@ -496,7 +493,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p0-g1', id: 'p0-g1',
groupId: 'g1', groupId: 'g1',
@ -507,7 +504,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}) })
authenticatedUser = await peterLustig.toJson() authenticatedUser = await peterLustig.toJson()
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p1-g1', id: 'p1-g1',
groupId: 'g1', groupId: 'g1',
@ -519,7 +516,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await bobDerBaumeister.toJson() authenticatedUser = await bobDerBaumeister.toJson()
await mutate({ await mutate({
mutation: CreateGroup, mutation: createGroupMutation(),
variables: { variables: {
id: 'g2', id: 'g2',
name: 'Yoga Practice', name: 'Yoga Practice',
@ -531,35 +528,35 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u3', userId: 'u3',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u4', userId: 'u4',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u5', userId: 'u5',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u6', userId: 'u6',
}, },
}) })
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u7', userId: 'u7',
@ -567,7 +564,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u3', userId: 'u3',
@ -575,7 +572,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u4', userId: 'u4',
@ -583,7 +580,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u5', userId: 'u5',
@ -591,7 +588,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: ChangeGroupMemberRole, mutation: changeGroupMemberRoleMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: 'u6', userId: 'u6',
@ -601,7 +598,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await louie.toJson() authenticatedUser = await louie.toJson()
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p0-g2', id: 'p0-g2',
groupId: 'g2', groupId: 'g2',
@ -617,7 +614,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
const now = new Date() const now = new Date()
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'e0', id: 'e0',
title: 'Illegaler Kindergeburtstag', title: 'Illegaler Kindergeburtstag',
@ -632,7 +629,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'e1', id: 'e1',
title: 'Wir Schützen den Stuttgarter Schlossgarten', title: 'Wir Schützen den Stuttgarter Schlossgarten',
@ -647,7 +644,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'e2', id: 'e2',
title: 'IT 4 Change Treffen', title: 'IT 4 Change Treffen',
@ -851,7 +848,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
'The new physics of <a class="hashtag" data-hashtag-id="QuantenFlussTheorie" href="/?hashtag=QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" data-hashtag-id="QuantumGravity" href="/?hashtag=QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> got that already. ;-)' 'The new physics of <a class="hashtag" data-hashtag-id="QuantenFlussTheorie" href="/?hashtag=QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" data-hashtag-id="QuantumGravity" href="/?hashtag=QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> got that already. ;-)'
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p2', id: 'p2',
title: `Nature Philosophy Yoga`, title: `Nature Philosophy Yoga`,
@ -860,7 +857,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p7', id: 'p7',
title: 'This is post #7', title: 'This is post #7',
@ -869,7 +866,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p8', id: 'p8',
title: `Quantum Flow Theory explains Quantum Gravity`, title: `Quantum Flow Theory explains Quantum Gravity`,
@ -878,7 +875,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreatePost, mutation: createPostMutation(),
variables: { variables: {
id: 'p12', id: 'p12',
title: 'This is post #12', title: 'This is post #12',
@ -902,7 +899,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
const mentionInComment2 = const mentionInComment2 =
'Did <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> tell you?' 'Did <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> tell you?'
await mutate({ await mutate({
mutation: CreateComment, mutation: createCommentMutation,
variables: { variables: {
id: 'c4', id: 'c4',
postId: 'p2', postId: 'p2',
@ -910,7 +907,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreateComment, mutation: createCommentMutation,
variables: { variables: {
id: 'c4-1', id: 'c4-1',
postId: 'p2', postId: 'p2',
@ -918,7 +915,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}, },
}) })
await mutate({ await mutate({
mutation: CreateComment, mutation: createCommentMutation,
variables: { variables: {
postId: 'p14', postId: 'p14',
content: faker.lorem.paragraph(), content: faker.lorem.paragraph(),
@ -1226,7 +1223,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = userObj authenticatedUser = userObj
await mutate({ await mutate({
mutation: JoinGroup, mutation: joinGroupMutation(),
variables: { variables: {
groupId: 'g2', groupId: 'g2',
userId: userObj.id, userId: userObj.id,
@ -1533,7 +1530,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
console.log('seed', 'chat') console.log('seed', 'chat')
authenticatedUser = await huey.toJson() authenticatedUser = await huey.toJson()
const { data: roomHueyPeter } = await mutate({ const { data: roomHueyPeter } = await mutate({
mutation: CreateRoom, mutation: createRoomMutation(),
variables: { variables: {
userId: (await peterLustig.toJson()).id, userId: (await peterLustig.toJson()).id,
}, },
@ -1560,7 +1557,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await huey.toJson() authenticatedUser = await huey.toJson()
const { data: roomHueyJenny } = await mutate({ const { data: roomHueyJenny } = await mutate({
mutation: CreateRoom, mutation: createRoomMutation(),
variables: { variables: {
userId: (await jennyRostock.toJson()).id, userId: (await jennyRostock.toJson()).id,
}, },
@ -1587,7 +1584,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
for (const user of additionalUsers.slice(0, 99)) { for (const user of additionalUsers.slice(0, 99)) {
authenticatedUser = await jennyRostock.toJson() authenticatedUser = await jennyRostock.toJson()
const { data: room } = await mutate({ const { data: room } = await mutate({
mutation: CreateRoom, mutation: createRoomMutation(),
variables: { variables: {
userId: (await user.toJson()).id, userId: (await user.toJson()).id,
}, },

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface CategoryDbProperties { export interface CategoryDbProperties {
createdAt: string createdAt: string

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface CommentDbProperties { export interface CommentDbProperties {
content: string content: string

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface EmailAddressDbProperties { export interface EmailAddressDbProperties {
createdAt: string createdAt: string

View File

@ -1,5 +1,6 @@
import type { PostDbProperties } from './Post' import { Integer, Node } from 'neo4j-driver'
import type { Integer, Node } from 'neo4j-driver'
import { PostDbProperties } from './Post'
export interface EventDbProperties extends PostDbProperties { export interface EventDbProperties extends PostDbProperties {
eventIsOnline: boolean eventIsOnline: boolean

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface GroupDbProperties { export interface GroupDbProperties {
about: string about: string

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface ImageDbProperties { export interface ImageDbProperties {
alt: string alt: string

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface InviteCodeDbProperties { export interface InviteCodeDbProperties {
code: string code: string

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface LocationDbProperties { export interface LocationDbProperties {
id: string id: string
@ -14,7 +14,6 @@ export interface LocationDbProperties {
namePL: string namePL: string
namePT: string namePT: string
nameRU: string nameRU: string
nameSQ: string
type: string type: string
} }

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface MessageDbProperties { export interface MessageDbProperties {
content: string content: string

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface PostDbProperties { export interface PostDbProperties {
clickedCount: number clickedCount: number

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface ReportDbProperties { export interface ReportDbProperties {
closed: boolean closed: boolean

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface TagDbProperties { export interface TagDbProperties {
deleted: boolean deleted: boolean

View File

@ -1,4 +1,4 @@
import type { Integer, Node } from 'neo4j-driver' import { Integer, Node } from 'neo4j-driver'
export interface UserDbProperties { export interface UserDbProperties {
allowEmbedIframes: boolean allowEmbedIframes: boolean

View File

@ -1,12 +1,9 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendChatMessageMail English chat_message template 1`] = ` exports[`sendChatMessageMail English chat_message template 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -16,10 +13,9 @@ exports[`sendChatMessageMail English chat_message template 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -27,10 +23,9 @@ exports[`sendChatMessageMail English chat_message template 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -41,17 +36,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -62,9 +51,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -72,6 +62,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -80,7 +74,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -100,13 +94,13 @@ footer {
<p>you have received a new chat message from <a class="user" href="http://webapp:3000/profile/chatSender/chatsender">chatSender</a>. <p>you have received a new chat message from <a class="user" href="http://webapp:3000/profile/chatSender/chatsender">chatSender</a>.
</p><a class="button" href="http://webapp:3000/chat">Show Chat</a> </p><a class="button" href="http://webapp:3000/chat">Show Chat</a>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p><br> <p> The ocelot.social Team</p><br>
<p class="no-margin-top-bottom">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> <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> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -135,20 +129,14 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "chatReceiver",
},
} }
`; `;
exports[`sendChatMessageMail German chat_message template 1`] = ` exports[`sendChatMessageMail German chat_message template 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -158,10 +146,9 @@ exports[`sendChatMessageMail German chat_message template 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -169,10 +156,9 @@ exports[`sendChatMessageMail German chat_message template 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -183,17 +169,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -204,9 +184,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -214,6 +195,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -222,7 +207,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -242,13 +227,13 @@ footer {
<p>du hast eine neue Chat-Nachricht von <a class="user" href="http://webapp:3000/profile/chatSender/chatsender">chatSender</a> erhalten. <p>du hast eine neue Chat-Nachricht von <a class="user" href="http://webapp:3000/profile/chatSender/chatsender">chatSender</a> erhalten.
</p><a class="button" href="http://webapp:3000/chat">Chat anzeigen</a> </p><a class="button" href="http://webapp:3000/chat">Chat anzeigen</a>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p><br> <p> Dein ocelot.social Team</p><br>
<p class="no-margin-top-bottom">PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine <a class="settings" href="http://webapp:3000/settings/notifications">Benachrichtigungseinstellungen</a>!</p> <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> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">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> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -277,9 +262,6 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "chatReceiver",
},
} }
`; `;

View File

@ -1,12 +1,9 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendEmailVerification English renders correctly 1`] = ` exports[`sendEmailVerification English renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -16,10 +13,9 @@ exports[`sendEmailVerification English renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -27,10 +23,9 @@ exports[`sendEmailVerification English renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -41,17 +36,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -62,9 +51,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -72,6 +62,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -80,7 +74,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -101,12 +95,12 @@ footer {
<p>If you don't want to change your e-mail address feel free to ignore this message. </p> <p>If you don't want to change your e-mail address feel free to ignore this message. </p>
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></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"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p> <p> The ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -138,20 +132,14 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "User",
},
} }
`; `;
exports[`sendEmailVerification German renders correctly 1`] = ` exports[`sendEmailVerification German renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -161,10 +149,9 @@ exports[`sendEmailVerification German renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -172,10 +159,9 @@ exports[`sendEmailVerification German renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -186,17 +172,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -207,9 +187,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -217,6 +198,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -225,7 +210,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -246,12 +231,12 @@ footer {
<p>Falls du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht einfach ignorieren. </p> <p>Falls du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht 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> <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"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p> <p> Dein ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">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> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -283,9 +268,6 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "User",
},
} }
`; `;

View File

@ -1,12 +1,9 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendRegistrationMail with invite code English renders correctly 1`] = ` exports[`sendRegistrationMail with invite code English renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -16,10 +13,9 @@ exports[`sendRegistrationMail with invite code English renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -27,10 +23,9 @@ exports[`sendRegistrationMail with invite code English renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -41,17 +36,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -62,9 +51,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -72,6 +62,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -80,7 +74,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -97,19 +91,19 @@ footer {
<h2>Welcome to ocelot.social!</h2> <h2>Welcome to ocelot.social!</h2>
<div class="wrapper"> <div class="wrapper">
<div class="content"></div> <div class="content"></div>
<p>Thank you for joining our cause it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=moderator%40example.org&amp;nonce=123456&amp;inviteCode=welcome&amp;method=invite-code">Confirm your e-mail address</a> <p>Thank you for joining our cause it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;inviteCode=welcome&amp;method=invite-code">Confirm your e-mail address</a>
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p> <p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
<p>However, this only works if you have registered through our website.</p> <p>However, this only works if you have registered through our website.</p>
<p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together. <p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.
</p> </p>
<p>PS: If you ignore this e-mail we will not create an account for you. ;)</p> <p>PS: If you ignore this e-mail we will not create an account for you. ;)</p>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p> <p> The ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -125,7 +119,7 @@ just one tiny step missing before we can start shaping the world together …
Please confirm your e-mail address by clicking the button below: Please confirm your e-mail address by clicking the button below:
Confirm your e-mail address Confirm your e-mail address
[http://webapp:3000/registration?email=moderator%40example.org&nonce=123456&inviteCode=welcome&method=invite-code] [http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code]
If the above button doesn't work, you can also copy the following code into your If the above button doesn't work, you can also copy the following code into your
browser window: 123456 browser window: 123456
@ -147,20 +141,14 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "moderator@example.org",
"name": "Bob &"?@\\ Baumeister",
},
} }
`; `;
exports[`sendRegistrationMail with invite code German renders correctly 1`] = ` exports[`sendRegistrationMail with invite code German renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -170,10 +158,9 @@ exports[`sendRegistrationMail with invite code German renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -181,10 +168,9 @@ exports[`sendRegistrationMail with invite code German renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -195,17 +181,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -216,9 +196,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -226,6 +207,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -234,7 +219,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -251,19 +236,19 @@ footer {
<h2>Willkommen bei ocelot.social!</h2> <h2>Willkommen bei ocelot.social!</h2>
<div class="wrapper"> <div class="wrapper">
<div class="content"></div> <div class="content"></div>
<p>Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=moderator%40example.org&amp;nonce=123456&amp;inviteCode=welcome&amp;method=invite-code">Bestätige deine E-Mail Adresse</a> <p>Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;inviteCode=welcome&amp;method=invite-code">Bestätige deine E-Mail Adresse</a>
<p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in dein Browserfenster kopieren: <span>123456</span></p> <p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in dein Browserfenster kopieren: <span>123456</span></p>
<p>Das funktioniert allerdings nur, wenn du dich über unsere Website registriert hast.</p> <p>Das funktioniert allerdings nur, wenn du dich über unsere Website registriert hast.</p>
<p>Falls du dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen. <p>Falls du dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.
</p> </p>
<p>PS: Wenn du keinen Account bei uns möchtest, kannst du diese E-Mail einfach ignorieren. ;)</p> <p>PS: Wenn du keinen Account bei uns möchtest, kannst du diese E-Mail einfach ignorieren. ;)</p>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p> <p> Dein ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">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> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -279,7 +264,7 @@ fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können
… Bitte bestätige deine E-Mail Adresse: … Bitte bestätige deine E-Mail Adresse:
Bestätige deine E-Mail Adresse Bestätige deine E-Mail Adresse
[http://webapp:3000/registration?email=moderator%40example.org&nonce=123456&inviteCode=welcome&method=invite-code] [http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code]
Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in
dein Browserfenster kopieren: 123456 dein Browserfenster kopieren: 123456
@ -302,20 +287,14 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "moderator@example.org",
"name": "Bob &"?@\\ Baumeister",
},
} }
`; `;
exports[`sendRegistrationMail without invite code English renders correctly 1`] = ` exports[`sendRegistrationMail without invite code English renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -325,10 +304,9 @@ exports[`sendRegistrationMail without invite code English renders correctly 1`]
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -336,10 +314,9 @@ exports[`sendRegistrationMail without invite code English renders correctly 1`]
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -350,17 +327,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -371,9 +342,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -381,6 +353,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -389,7 +365,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -406,19 +382,19 @@ footer {
<h2>Welcome to ocelot.social!</h2> <h2>Welcome to ocelot.social!</h2>
<div class="wrapper"> <div class="wrapper">
<div class="content"></div> <div class="content"></div>
<p>Thank you for joining our cause it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=moderator%40example.org&amp;nonce=123456&amp;method=invite-mail">Confirm your e-mail address</a> <p>Thank you for joining our cause it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;method=invite-mail">Confirm your e-mail address</a>
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p> <p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
<p>However, this only works if you have registered through our website.</p> <p>However, this only works if you have registered through our website.</p>
<p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together. <p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.
</p> </p>
<p>PS: If you ignore this e-mail we will not create an account for you. ;)</p> <p>PS: If you ignore this e-mail we will not create an account for you. ;)</p>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p> <p> The ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -434,7 +410,7 @@ just one tiny step missing before we can start shaping the world together …
Please confirm your e-mail address by clicking the button below: Please confirm your e-mail address by clicking the button below:
Confirm your e-mail address Confirm your e-mail address
[http://webapp:3000/registration?email=moderator%40example.org&nonce=123456&method=invite-mail] [http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail]
If the above button doesn't work, you can also copy the following code into your If the above button doesn't work, you can also copy the following code into your
browser window: 123456 browser window: 123456
@ -456,20 +432,14 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "moderator@example.org",
"name": "Bob &"?@\\ Baumeister",
},
} }
`; `;
exports[`sendRegistrationMail without invite code German renders correctly 1`] = ` exports[`sendRegistrationMail without invite code German renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -479,10 +449,9 @@ exports[`sendRegistrationMail without invite code German renders correctly 1`] =
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -490,10 +459,9 @@ exports[`sendRegistrationMail without invite code German renders correctly 1`] =
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -504,17 +472,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -525,9 +487,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -535,6 +498,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -543,7 +510,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -560,19 +527,19 @@ footer {
<h2>Willkommen bei ocelot.social!</h2> <h2>Willkommen bei ocelot.social!</h2>
<div class="wrapper"> <div class="wrapper">
<div class="content"></div> <div class="content"></div>
<p>Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=moderator%40example.org&amp;nonce=123456&amp;method=invite-mail">Bestätige deine E-Mail Adresse</a> <p>Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;method=invite-mail">Bestätige deine E-Mail Adresse</a>
<p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in dein Browserfenster kopieren: <span>123456</span></p> <p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in dein Browserfenster kopieren: <span>123456</span></p>
<p>Das funktioniert allerdings nur, wenn du dich über unsere Website registriert hast.</p> <p>Das funktioniert allerdings nur, wenn du dich über unsere Website registriert hast.</p>
<p>Falls du dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen. <p>Falls du dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.
</p> </p>
<p>PS: Wenn du keinen Account bei uns möchtest, kannst du diese E-Mail einfach ignorieren. ;)</p> <p>PS: Wenn du keinen Account bei uns möchtest, kannst du diese E-Mail einfach ignorieren. ;)</p>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p> <p> Dein ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">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> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -588,7 +555,7 @@ fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können
… Bitte bestätige deine E-Mail Adresse: … Bitte bestätige deine E-Mail Adresse:
Bestätige deine E-Mail Adresse Bestätige deine E-Mail Adresse
[http://webapp:3000/registration?email=moderator%40example.org&nonce=123456&method=invite-mail] [http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail]
Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in
dein Browserfenster kopieren: 123456 dein Browserfenster kopieren: 123456
@ -611,9 +578,6 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "moderator@example.org",
"name": "Bob &"?@\\ Baumeister",
},
} }
`; `;

View File

@ -1,12 +1,9 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendResetPasswordMail English renders correctly 1`] = ` exports[`sendResetPasswordMail English renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -16,10 +13,9 @@ exports[`sendResetPasswordMail English renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -27,10 +23,9 @@ exports[`sendResetPasswordMail English renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -41,17 +36,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -62,9 +51,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -72,6 +62,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -80,7 +74,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -101,12 +95,12 @@ footer {
<p>If you didn't request a new password feel free to ignore this e-mail.</p> <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> <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"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p> <p> The ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -137,20 +131,14 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "Jenny Rostock",
},
} }
`; `;
exports[`sendResetPasswordMail German renders correctly 1`] = ` exports[`sendResetPasswordMail German renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -160,10 +148,9 @@ exports[`sendResetPasswordMail German renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -171,10 +158,9 @@ exports[`sendResetPasswordMail German renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -185,17 +171,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -206,9 +186,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -216,6 +197,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -224,7 +209,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -245,12 +230,12 @@ footer {
<p>Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.</p> <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> <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"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p> <p> Dein ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">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> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -282,9 +267,6 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "Jenny Rostock",
},
} }
`; `;

View File

@ -1,12 +1,9 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendWrongEmail English renders correctly 1`] = ` exports[`sendWrongEmail English renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -16,10 +13,9 @@ exports[`sendWrongEmail English renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -27,10 +23,9 @@ exports[`sendWrongEmail English renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -41,17 +36,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -62,9 +51,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -72,6 +62,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -80,7 +74,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -101,12 +95,12 @@ footer {
<p>If you don't have an account at <a>ocelot.social</a> yet or if you didn't want to reset your password, please ignore this e-mail. <p>If you don't have an account at <a>ocelot.social</a> yet or if you didn't want to reset your password, please ignore this e-mail.
</p> </p>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p> <p> The ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">If you have questions or problems, feel free to contact our support: <a href="mailto:devops@ocelot.social">devops@ocelot.social</a></p> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -135,20 +129,14 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "moderator@example.org",
"name": "Bob &"?@\\ Baumeister",
},
} }
`; `;
exports[`sendWrongEmail German renders correctly 1`] = ` exports[`sendWrongEmail German renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -158,10 +146,9 @@ exports[`sendWrongEmail German renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -169,10 +156,9 @@ exports[`sendWrongEmail German renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -183,17 +169,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -204,9 +184,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -214,6 +195,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -222,7 +207,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -243,12 +228,12 @@ footer {
<p>Wenn du noch keinen Account bei <a>ocelot.social</a> hast oder dein Password gar nicht ändern willst, kannst du diese E-Mail einfach ignorieren! <p>Wenn du noch keinen Account bei <a>ocelot.social</a> hast oder dein Password gar nicht ändern willst, kannst du diese E-Mail einfach ignorieren!
</p> </p>
<div class="text-block"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p> <p> Dein ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">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> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -277,9 +262,6 @@ devops@ocelot.social [devops@ocelot.social]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "moderator@example.org",
"name": "Bob &"?@\\ Baumeister",
},
} }
`; `;

View File

@ -1,12 +1,9 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendResetPasswordMail with support English renders correctly 1`] = ` exports[`sendResetPasswordMail with support English renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -16,10 +13,9 @@ exports[`sendResetPasswordMail with support English renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -27,10 +23,9 @@ exports[`sendResetPasswordMail with support English renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -41,17 +36,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -62,9 +51,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -72,6 +62,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -80,7 +74,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -101,12 +95,12 @@ footer {
<p>If you didn't request a new password feel free to ignore this e-mail.</p> <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> <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"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p> <p> The ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">If you have questions or problems, feel free to contact our support: <a href="mailto:support@example.org">support@example.org</a></p> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -137,20 +131,14 @@ support@example.org [support@example.org]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "Jenny Rostock",
},
} }
`; `;
exports[`sendResetPasswordMail with support German renders correctly 1`] = ` exports[`sendResetPasswordMail with support German renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -160,10 +148,9 @@ exports[`sendResetPasswordMail with support German renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -171,10 +158,9 @@ exports[`sendResetPasswordMail with support German renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -185,17 +171,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -206,9 +186,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -216,6 +197,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -224,7 +209,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -245,12 +230,12 @@ footer {
<p>Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.</p> <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> <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"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p> <p> Dein ocelot.social Team</p>
</div> </div>
</div> </div>
<div class="support"> <div class="support">
<p class="no-margin-top-bottom">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> <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> </div>
<footer> <footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a> <div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
@ -282,20 +267,14 @@ support@example.org [support@example.org]
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "Jenny Rostock",
},
} }
`; `;
exports[`sendResetPasswordMail without support English renders correctly 1`] = ` exports[`sendResetPasswordMail without support English renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -305,10 +284,9 @@ exports[`sendResetPasswordMail without support English renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -316,10 +294,9 @@ exports[`sendResetPasswordMail without support English renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -330,17 +307,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -351,9 +322,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -361,6 +333,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -369,7 +345,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -390,8 +366,8 @@ footer {
<p>If you didn't request a new password feel free to ignore this e-mail.</p> <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> <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"> <div class="text-block">
<p class="no-margin-top-bottom">See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> The ocelot.social Team</p> <p> The ocelot.social Team</p>
</div> </div>
</div> </div>
<footer> <footer>
@ -420,20 +396,14 @@ See you soon on ocelot.social [https://ocelot.social]!
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "Jenny Rostock",
},
} }
`; `;
exports[`sendResetPasswordMail without support German renders correctly 1`] = ` exports[`sendResetPasswordMail without support German renders correctly 1`] = `
{ {
"attachments": [], "attachments": [],
"from": { "from": "ocelot.social <devops@ocelot.social>",
"address": "devops@ocelot.social",
"name": "ocelot.social",
},
"html": "<!DOCTYPE html> "html": "<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -443,10 +413,9 @@ exports[`sendResetPasswordMail without support German renders correctly 1`] = `
<style>body{ <style>body{
display: block; display: block;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 17px;
text-align: left; text-align: left;
text-align: -webkit-left; text-align: -webkit-left;
line-height: 24px;
justify-content: center; justify-content: center;
padding: 15px; padding: 15px;
margin: 0px; margin: 0px;
@ -454,10 +423,9 @@ exports[`sendResetPasswordMail without support German renders correctly 1`] = `
h2 { h2 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 30px; font-size: 25px;
font-size: 24px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 22px;
color: #333333; color: #333333;
} }
@ -468,17 +436,11 @@ h2 {
} }
.head-logo { .head-logo {
width: 30%; width: 60%;
height: auto; height: auto;
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50px;
}
p.no-margin-top-bottom {
margin-top: 0;
margin-bottom: 0;
} }
a { a {
@ -489,9 +451,10 @@ a.button {
background: #17b53e; background: #17b53e;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 15px;
text-decoration: none; text-decoration: none;
text-align:center; text-align:center;
padding: 9px 18px; padding: 13px 17px;
color: #ffffff; color: #ffffff;
display: table; display: table;
margin-left: auto; margin-left: auto;
@ -499,6 +462,10 @@ a.button {
border-radius: 4px; border-radius: 4px;
} }
span {
color: #17b53e;
}
.text-block { .text-block {
margin-top: 20px; margin-top: 20px;
color: #000000; color: #000000;
@ -507,7 +474,7 @@ a.button {
footer { footer {
padding: 20px; padding: 20px;
font-family: Lato, sans-serif; font-family: Lato, sans-serif;
font-size: 14px; font-size: 12px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
color: #888888; color: #888888;
@ -528,8 +495,8 @@ footer {
<p>Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.</p> <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> <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"> <div class="text-block">
<p class="no-margin-top-bottom">Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p> <p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p class="no-margin-top-bottom"> Dein ocelot.social Team</p> <p> Dein ocelot.social Team</p>
</div> </div>
</div> </div>
<footer> <footer>
@ -559,9 +526,6 @@ Bis bald bei ocelot.social [https://ocelot.social]!
ocelot.social Community [https://ocelot.social]", ocelot.social Community [https://ocelot.social]",
"to": { "to": "user@example.org",
"address": "user@example.org",
"name": "Jenny Rostock",
},
} }
`; `;

View File

@ -2,7 +2,7 @@ import CONFIG from '@config/index'
CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social' CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social'
// eslint-disable-next-line import-x/first // eslint-disable-next-line import/first
import { sendChatMessageMail } from './sendEmail' import { sendChatMessageMail } from './sendEmail'
const senderUser = { const senderUser = {

View File

@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import path from 'node:path' import path from 'node:path'
import Email from 'email-templates' import Email from 'email-templates'
@ -12,8 +13,7 @@ import { createTransport } from 'nodemailer'
import CONFIG, { nodemailerTransportOptions } from '@config/index' import CONFIG, { nodemailerTransportOptions } from '@config/index'
import logosWebapp from '@config/logosBranded' import logosWebapp from '@config/logosBranded'
import metadata from '@config/metadata' import metadata from '@config/metadata'
import { UserDbProperties } from '@db/types/User'
import type { UserDbProperties } from '@db/types/User'
const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI)
const settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI) const settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI)
@ -29,7 +29,7 @@ export const defaultParams = {
renderSettingsUrl: true, renderSettingsUrl: true,
} }
const from = { name: CONFIG.APPLICATION_NAME, address: CONFIG.EMAIL_DEFAULT_SENDER } const from = `${CONFIG.APPLICATION_NAME} <${CONFIG.EMAIL_DEFAULT_SENDER}>`
const transport = createTransport(nodemailerTransportOptions) const transport = createTransport(nodemailerTransportOptions)
@ -74,8 +74,8 @@ interface OriginalMessage {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const sendNotificationMail = async (notification: any): Promise<OriginalMessage> => { export const sendNotificationMail = async (notification: any): Promise<OriginalMessage> => {
const locale = notification?.to?.locale const locale = notification?.to?.locale
const to = notification?.email
const name = notification?.to?.name const name = notification?.to?.name
const to = { name, address: notification?.email }
const template = notification?.reason const template = notification?.reason
try { try {
@ -94,8 +94,8 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
: notification?.from?.title, : notification?.from?.title,
postUrl: new URL( postUrl: new URL(
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? `/post/${encodeURIComponent(notification?.from?.post?.id)}/${encodeURIComponent(notification?.from?.post?.slug)}` ? `/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}`
: `/post/${encodeURIComponent(notification?.from?.id)}/${encodeURIComponent(notification?.from?.slug)}`, : `/post/${notification?.from?.id}/${notification?.from?.slug}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
), ),
postAuthorName: postAuthorName:
@ -106,7 +106,7 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? undefined ? undefined
: new URL( : new URL(
`profile/${encodeURIComponent(notification?.from?.author?.id)}/${encodeURIComponent(notification?.from?.author?.slug)}`, `user/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
), ),
commenterName: commenterName:
@ -116,14 +116,14 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
commenterUrl: commenterUrl:
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? new URL( ? new URL(
`/profile/${encodeURIComponent(notification?.from?.author?.id)}/${encodeURIComponent(notification?.from?.author?.slug)}`, `/profile/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
commentUrl: commentUrl:
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? new URL( ? new URL(
`/post/${encodeURIComponent(notification?.from?.post?.id)}/${encodeURIComponent(notification?.from?.post?.slug)}#commentId-${encodeURIComponent(notification?.from?.id)}`, `/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}#commentId-${notification?.from?.id}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
@ -132,7 +132,7 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
groupUrl: groupUrl:
notification?.from?.__typename === 'Group' notification?.from?.__typename === 'Group'
? new URL( ? new URL(
`/groups/${encodeURIComponent(notification?.from?.id)}/${encodeURIComponent(notification?.from?.slug)}`, `/groups/${notification?.from?.id}/${notification?.from?.slug}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
@ -143,7 +143,7 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
groupRelatedUserUrl: groupRelatedUserUrl:
notification?.from?.__typename === 'Group' notification?.from?.__typename === 'Group'
? new URL( ? new URL(
`/profile/${encodeURIComponent(notification?.relatedUser?.id)}/${encodeURIComponent(notification?.relatedUser?.slug)}`, `/profile/${notification?.relatedUser?.id}/${notification?.relatedUser?.slug}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
@ -165,7 +165,7 @@ export const sendChatMessageMail = async (
data: ChatMessageEmailInput, data: ChatMessageEmailInput,
): Promise<OriginalMessage> => { ): Promise<OriginalMessage> => {
const { senderUser, recipientUser } = data const { senderUser, recipientUser } = data
const to = { name: recipientUser.name, address: data.email } const to = data.email
try { try {
const { originalMessage } = await email.send({ const { originalMessage } = await email.send({
template: path.join(__dirname, 'templates', 'chat_message'), template: path.join(__dirname, 'templates', 'chat_message'),
@ -177,10 +177,7 @@ export const sendChatMessageMail = async (
locale: recipientUser.locale, locale: recipientUser.locale,
name: recipientUser.name, name: recipientUser.name,
chattingUser: senderUser.name, chattingUser: senderUser.name,
chattingUserUrl: new URL( chattingUserUrl: new URL(`/profile/${senderUser.id}/${senderUser.slug}`, CONFIG.CLIENT_URI),
`/profile/${encodeURIComponent(senderUser.id)}/${encodeURIComponent(senderUser.slug)}`,
CONFIG.CLIENT_URI,
),
chatUrl: new URL('/chat', CONFIG.CLIENT_URI), chatUrl: new URL('/chat', CONFIG.CLIENT_URI),
}, },
}) })
@ -191,7 +188,6 @@ export const sendChatMessageMail = async (
} }
interface VerifyMailInput { interface VerifyMailInput {
name: string
email: string email: string
nonce: string nonce: string
locale: string locale: string
@ -204,10 +200,10 @@ interface RegistrationMailInput extends VerifyMailInput {
export const sendRegistrationMail = async ( export const sendRegistrationMail = async (
data: RegistrationMailInput, data: RegistrationMailInput,
): Promise<OriginalMessage> => { ): Promise<OriginalMessage> => {
const { name, nonce, locale, inviteCode } = data const { nonce, locale, inviteCode } = data
const to = { name, address: data.email } const to = data.email
const actionUrl = new URL('/registration', CONFIG.CLIENT_URI) const actionUrl = new URL('/registration', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('email', to.address) actionUrl.searchParams.set('email', to)
actionUrl.searchParams.set('nonce', nonce) actionUrl.searchParams.set('nonce', nonce)
if (inviteCode) { if (inviteCode) {
actionUrl.searchParams.set('inviteCode', inviteCode) actionUrl.searchParams.set('inviteCode', inviteCode)
@ -244,9 +240,9 @@ export const sendEmailVerification = async (
data: EmailVerificationInput, data: EmailVerificationInput,
): Promise<OriginalMessage> => { ): Promise<OriginalMessage> => {
const { nonce, locale, name } = data const { nonce, locale, name } = data
const to = { name, address: data.email } const to = data.email
const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI) const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('email', to.address) actionUrl.searchParams.set('email', to)
actionUrl.searchParams.set('nonce', nonce) actionUrl.searchParams.set('nonce', nonce)
try { try {
@ -274,9 +270,9 @@ export const sendResetPasswordMail = async (
data: EmailVerificationInput, data: EmailVerificationInput,
): Promise<OriginalMessage> => { ): Promise<OriginalMessage> => {
const { nonce, locale, name } = data const { nonce, locale, name } = data
const to = { name, address: data.email } const to = data.email
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI) const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('email', to.address) actionUrl.searchParams.set('email', to)
actionUrl.searchParams.set('nonce', nonce) actionUrl.searchParams.set('nonce', nonce)
try { try {
const { originalMessage } = await email.send({ const { originalMessage } = await email.send({
@ -300,12 +296,11 @@ export const sendResetPasswordMail = async (
} }
export const sendWrongEmail = async (data: { export const sendWrongEmail = async (data: {
name: string
locale: string locale: string
email: string email: string
}): Promise<OriginalMessage> => { }): Promise<OriginalMessage> => {
const { locale, name } = data const { locale } = data
const to = { name, address: data.email } const to = data.email
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI) const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
try { try {
const { originalMessage } = await email.send({ const { originalMessage } = await email.send({

View File

@ -2,7 +2,7 @@ import CONFIG from '@config/index'
CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social' CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social'
// eslint-disable-next-line import-x/first // eslint-disable-next-line import/first
import { sendEmailVerification } from './sendEmail' import { sendEmailVerification } from './sendEmail'
describe('sendEmailVerification', () => { describe('sendEmailVerification', () => {

View File

@ -2,7 +2,7 @@ import CONFIG from '@config/index'
CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social' CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social'
// eslint-disable-next-line import-x/first // eslint-disable-next-line import/first
import { sendNotificationMail } from './sendEmail' import { sendNotificationMail } from './sendEmail'
describe('sendNotificationMail', () => { describe('sendNotificationMail', () => {

View File

@ -2,19 +2,17 @@ import CONFIG from '@config/index'
CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social' CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social'
// eslint-disable-next-line import-x/first // eslint-disable-next-line import/first
import { sendRegistrationMail } from './sendEmail' import { sendRegistrationMail } from './sendEmail'
describe('sendRegistrationMail', () => { describe('sendRegistrationMail', () => {
const data: { const data: {
name: string
email: string email: string
nonce: string nonce: string
locale: string locale: string
inviteCode?: string inviteCode?: string
} = { } = {
name: 'Bob &"?@\\ Baumeister', email: 'user@example.org',
email: 'moderator@example.org',
nonce: '123456', nonce: '123456',
locale: 'en', locale: 'en',
inviteCode: 'welcome', inviteCode: 'welcome',

View File

@ -2,7 +2,7 @@ import CONFIG from '@config/index'
CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social' CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social'
// eslint-disable-next-line import-x/first // eslint-disable-next-line import/first
import { sendResetPasswordMail } from './sendEmail' import { sendResetPasswordMail } from './sendEmail'
describe('sendResetPasswordMail', () => { describe('sendResetPasswordMail', () => {

View File

@ -2,17 +2,15 @@ import CONFIG from '@config/index'
CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social' CONFIG.SUPPORT_EMAIL = 'devops@ocelot.social'
// eslint-disable-next-line import-x/first // eslint-disable-next-line import/first
import { sendWrongEmail } from './sendEmail' import { sendWrongEmail } from './sendEmail'
describe('sendWrongEmail', () => { describe('sendWrongEmail', () => {
const data: { const data: {
name: string
email: string email: string
locale: string locale: string
} = { } = {
name: 'Bob &"?@\\ Baumeister', email: 'user@example.org',
email: 'moderator@example.org',
locale: 'en', locale: 'en',
} }

View File

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

View File

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

View File

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

View File

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

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