mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-01-20 20:01:25 +00:00
Merge branch 'master' of github.com:Ocelot-Social-Community/Ocelot-Social into brand-reformer-network-first-step
This commit is contained in:
commit
713de2006e
166
.github/dependabot.yml
vendored
166
.github/dependabot.yml
vendored
@ -22,7 +22,7 @@ updates:
|
||||
babel:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "@babel/*"
|
||||
- "@babel*"
|
||||
cypress:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
@ -53,14 +53,14 @@ updates:
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
groups:
|
||||
apollo:
|
||||
apollo-server:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "*apollo*"
|
||||
- "*apollo-server*"
|
||||
babel:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "*babel*"
|
||||
- "@babel*"
|
||||
metascraper:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
@ -99,10 +99,6 @@ updates:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "@babel*"
|
||||
eslint:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "eslint*"
|
||||
jest:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
@ -111,10 +107,6 @@ updates:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "*mapbox*"
|
||||
sass:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "sass*"
|
||||
storybook:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
@ -145,78 +137,78 @@ updates:
|
||||
time: "03:00"
|
||||
|
||||
# frontend
|
||||
- package-ecosystem: npm
|
||||
open-pull-requests-limit: 99
|
||||
directory: "/frontend"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
groups:
|
||||
eslint:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "eslint*"
|
||||
- "@eslint*"
|
||||
pinia:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "pinia*"
|
||||
react:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "react*"
|
||||
remark:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "remark*"
|
||||
storybook:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "storybook"
|
||||
- "@storybook*"
|
||||
stylelint:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "stylelint*"
|
||||
typescript:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "ts*"
|
||||
- "@types*"
|
||||
- "typescript"
|
||||
vite:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "vite"
|
||||
- "vite-plugin*"
|
||||
- "@vitejs/plugin-vue"
|
||||
vitest:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "vitest"
|
||||
- "@vitest*"
|
||||
vue:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "*vue?(/)*"
|
||||
exclude-patterns:
|
||||
- "vuetify"
|
||||
- "*vuepress*"
|
||||
- "vue-tsc"
|
||||
vuepress:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "vuepress"
|
||||
- "@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"
|
||||
# - package-ecosystem: npm
|
||||
# open-pull-requests-limit: 99
|
||||
# directory: "/frontend"
|
||||
# rebase-strategy: "disabled"
|
||||
# schedule:
|
||||
# interval: weekly
|
||||
# day: "saturday"
|
||||
# timezone: "Europe/Berlin"
|
||||
# time: "03:00"
|
||||
# groups:
|
||||
# eslint:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "eslint*"
|
||||
# - "@eslint*"
|
||||
# pinia:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "pinia*"
|
||||
# react:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "react*"
|
||||
# remark:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "remark*"
|
||||
# storybook:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "storybook"
|
||||
# - "@storybook*"
|
||||
# stylelint:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "stylelint*"
|
||||
# typescript:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "ts*"
|
||||
# - "@types*"
|
||||
# - "typescript"
|
||||
# vite:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "vite"
|
||||
# - "vite-plugin*"
|
||||
# - "@vitejs/plugin-vue"
|
||||
# vitest:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "vitest"
|
||||
# - "@vitest*"
|
||||
# vue:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "*vue?(/)*"
|
||||
# exclude-patterns:
|
||||
# - "vuetify"
|
||||
# - "*vuepress*"
|
||||
# - "vue-tsc"
|
||||
# vuepress:
|
||||
# applies-to: version-updates
|
||||
# patterns:
|
||||
# - "vuepress"
|
||||
# - "@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"
|
||||
|
||||
8
.github/workflows/check-documentation.yml
vendored
8
.github/workflows/check-documentation.yml
vendored
@ -30,11 +30,11 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
|
||||
- name: Remove old documentation files
|
||||
run: rm -rf ./deployment/src/old/ ./CHANGELOG.md # workaround until https://github.com/gaurav-nelson/github-action-markdown-link-check/pull/183 has been done
|
||||
- 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
|
||||
|
||||
- name: Check Markdown Links
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@7d83e59a57f3c201c76eed3d33dff64ec4452d27 # 1.0.15
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@1b916f2cf6c36510a6059943104e3c42ce6c16bc # 1.0.15
|
||||
with:
|
||||
use-quiet-mode: 'yes'
|
||||
use-verbose-mode: 'no'
|
||||
@ -54,7 +54,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
|
||||
- name: Setup Node 20
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.0.3
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
4
.github/workflows/deploy-documentation.yml
vendored
4
.github/workflows/deploy-documentation.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
|
||||
- name: Setup Node 20
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.0.3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
run: npm install && npm run docs:build
|
||||
|
||||
- name: Deploy Vuepress to Github Pages
|
||||
uses: crazy-max/ghaction-github-pages@fbf0a4fa4e00f45accd6cf3232368436ec06ed59 # v4.0.0
|
||||
uses: crazy-max/ghaction-github-pages@df5cc2bfa78282ded844b354faee141f06b41865 # v4.0.0
|
||||
with:
|
||||
target_branch: gh-pages
|
||||
build_dir: .vuepress/dist
|
||||
|
||||
91
.github/workflows/docker-push.yml
vendored
Normal file
91
.github/workflows/docker-push.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
name: docker-push
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
build-and-push-images:
|
||||
strategy:
|
||||
matrix:
|
||||
app:
|
||||
- name: neo4j
|
||||
context: neo4j
|
||||
file: neo4j/Dockerfile
|
||||
target: community
|
||||
- name: backend-base
|
||||
context: backend
|
||||
file: backend/Dockerfile
|
||||
target: base
|
||||
- name: backend-build
|
||||
context: backend
|
||||
file: backend/Dockerfile
|
||||
target: build
|
||||
- name: backend
|
||||
context: backend
|
||||
file: backend/Dockerfile
|
||||
target: production
|
||||
- name: webapp-base
|
||||
context: webapp
|
||||
file: webapp/Dockerfile
|
||||
target: base
|
||||
- name: webapp-build
|
||||
context: webapp
|
||||
file: webapp/Dockerfile
|
||||
target: build
|
||||
- name: webapp
|
||||
context: webapp
|
||||
file: webapp/Dockerfile
|
||||
target: production
|
||||
- name: maintenance-base
|
||||
context: webapp
|
||||
file: webapp/Dockerfile.maintenance
|
||||
target: base
|
||||
- name: maintenance-build
|
||||
context: webapp
|
||||
file: webapp/Dockerfile.maintenance
|
||||
target: build
|
||||
- name: maintenance
|
||||
context: webapp
|
||||
file: webapp/Dockerfile.maintenance
|
||||
target: production
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}/${{ matrix.app.name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=sha
|
||||
- name: Build and push Docker images
|
||||
id: push
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4
|
||||
with:
|
||||
context: ${{ matrix.app.context }}
|
||||
target: ${{ matrix.app.target }}
|
||||
file: ${{ matrix.app.file }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
235
.github/workflows/publish.yml
vendored
235
.github/workflows/publish.yml
vendored
@ -6,241 +6,12 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD COMMUNITY NEO4J ##########################################
|
||||
##############################################################################
|
||||
build_production_neo4j:
|
||||
name: Docker Build Production - Neo4J
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: Neo4J | Build `community` image
|
||||
run: |
|
||||
docker build --target community \
|
||||
--tag "ocelotsocialnetwork/neo4j-community:latest" \
|
||||
--tag "ocelotsocialnetwork/neo4j-community:${VERSION}" \
|
||||
--tag "ocelotsocialnetwork/neo4j-community:${BUILD_VERSION}" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
neo4j/
|
||||
- name: Neo4J | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/neo4j-community" > /tmp/neo4j.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: docker-neo4j-community
|
||||
path: /tmp/neo4j.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD PRODUCTION BACKEND #######################################
|
||||
##############################################################################
|
||||
build_production_backend:
|
||||
name: Docker Build Production - Backend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: Backend | Build `production` image
|
||||
run: |
|
||||
docker build --target base \
|
||||
--tag "ocelotsocialnetwork/backend:latest-base" \
|
||||
--tag "ocelotsocialnetwork/backend:${VERSION}-base" \
|
||||
--tag "ocelotsocialnetwork/backend:${BUILD_VERSION}-base" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
backend/
|
||||
docker build --target code \
|
||||
--tag "ocelotsocialnetwork/backend:latest-code" \
|
||||
--tag "ocelotsocialnetwork/backend:${VERSION}-code" \
|
||||
--tag "ocelotsocialnetwork/backend:${BUILD_VERSION}-code" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
backend/
|
||||
docker build --target production \
|
||||
--tag "ocelotsocialnetwork/backend:latest" \
|
||||
--tag "ocelotsocialnetwork/backend:${VERSION}" \
|
||||
--tag "ocelotsocialnetwork/backend:${BUILD_VERSION}" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
backend/
|
||||
- name: Backend | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/backend" > /tmp/backend.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: docker-backend-production
|
||||
path: /tmp/backend.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD PRODUCTION WEBAPP ########################################
|
||||
##############################################################################
|
||||
build_production_webapp:
|
||||
name: Docker Build Production - WebApp
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: Webapp | Build `production` image
|
||||
run: |
|
||||
docker build --target base \
|
||||
--tag "ocelotsocialnetwork/webapp:latest-base" \
|
||||
--tag "ocelotsocialnetwork/webapp:${VERSION}-base" \
|
||||
--tag "ocelotsocialnetwork/webapp:${BUILD_VERSION}-base" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
webapp/
|
||||
docker build --target code \
|
||||
--tag "ocelotsocialnetwork/webapp:latest-code" \
|
||||
--tag "ocelotsocialnetwork/webapp:${VERSION}-code" \
|
||||
--tag "ocelotsocialnetwork/webapp:${BUILD_VERSION}-code" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
webapp/
|
||||
docker build --target production \
|
||||
--tag "ocelotsocialnetwork/webapp:latest" \
|
||||
--tag "ocelotsocialnetwork/webapp:${VERSION}" \
|
||||
--tag "ocelotsocialnetwork/webapp:${BUILD_VERSION}" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
webapp/
|
||||
- name: Webapp | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/webapp" > /tmp/webapp.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: docker-webapp-production
|
||||
path: /tmp/webapp.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD PRODUCTION MAINTENANCE ###################################
|
||||
##############################################################################
|
||||
build_production_maintenance:
|
||||
name: Docker Build Production - Maintenance
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: Maintenance | Build `production` image
|
||||
run: |
|
||||
docker build --target base \
|
||||
--tag "ocelotsocialnetwork/maintenance:latest-base" \
|
||||
--tag "ocelotsocialnetwork/maintenance:${VERSION}-base" \
|
||||
--tag "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-base" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
-f webapp/Dockerfile.maintenance \
|
||||
webapp/
|
||||
docker build --target code \
|
||||
--tag "ocelotsocialnetwork/maintenance:latest-code" \
|
||||
--tag "ocelotsocialnetwork/maintenance:${VERSION}-code" \
|
||||
--tag "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-code" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
-f webapp/Dockerfile.maintenance \
|
||||
webapp/
|
||||
docker build --target production \
|
||||
--tag "ocelotsocialnetwork/maintenance:latest" \
|
||||
--tag "ocelotsocialnetwork/maintenance:${VERSION}" \
|
||||
--tag "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" \
|
||||
--build-arg BBUILD_DATE=$BUILD_DATE \
|
||||
--build-arg BBUILD_VERSION=$BUILD_VERSION \
|
||||
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
|
||||
-f webapp/Dockerfile.maintenance \
|
||||
webapp/
|
||||
- name: Maintenance | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/maintenance" > /tmp/maintenance.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: docker-maintenance-production
|
||||
path: /tmp/maintenance.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: UPLOAD TO DOCKERHUB ###################################################
|
||||
##############################################################################
|
||||
upload_to_dockerhub:
|
||||
name: Upload to Dockerhub
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_production_neo4j,build_production_backend,build_production_webapp,build_production_maintenance]
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Download Docker Image (Neo4J)
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: docker-neo4j-community
|
||||
path: /tmp
|
||||
- run: docker load < /tmp/neo4j.tar
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: docker-backend-production
|
||||
path: /tmp
|
||||
- run: docker load < /tmp/backend.tar
|
||||
- name: Download Docker Image (WebApp)
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: docker-webapp-production
|
||||
path: /tmp
|
||||
- run: docker load < /tmp/webapp.tar
|
||||
- name: Download Docker Image (Maintenance)
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: docker-maintenance-production
|
||||
path: /tmp
|
||||
- run: docker load < /tmp/maintenance.tar
|
||||
|
||||
- name: login to dockerhub
|
||||
run: echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
|
||||
- name: Push images
|
||||
run: |
|
||||
docker push --all-tags ocelotsocialnetwork/neo4j-community
|
||||
docker push --all-tags ocelotsocialnetwork/backend
|
||||
docker push --all-tags ocelotsocialnetwork/webapp
|
||||
docker push --all-tags ocelotsocialnetwork/maintenance
|
||||
|
||||
##############################################################################
|
||||
# JOB: GITHUB TAG LATEST VERSION #############################################
|
||||
##############################################################################
|
||||
github_tag:
|
||||
name: Tag latest version on Github
|
||||
runs-on: ubuntu-latest
|
||||
needs: [upload_to_dockerhub]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
@ -293,7 +64,7 @@ jobs:
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
#- name: Repository Dispatch
|
||||
# uses: peter-evans/repository-dispatch@cc7d8686e214c240128fe2680a5c1c5486ae19c4 # v3.0.0
|
||||
# uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0
|
||||
# with:
|
||||
# token: ${{ github.token }}
|
||||
# event-type: trigger-ocelot-build-success
|
||||
@ -301,7 +72,7 @@ jobs:
|
||||
# client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
- name: Repository Dispatch stage.ocelot.social
|
||||
uses: peter-evans/repository-dispatch@cc7d8686e214c240128fe2680a5c1c5486ae19c4 # v3.0.0
|
||||
uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
@ -309,7 +80,7 @@ jobs:
|
||||
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
- name: Repository Dispatch stage.yunite.me
|
||||
uses: peter-evans/repository-dispatch@cc7d8686e214c240128fe2680a5c1c5486ae19c4 # v3.0.0
|
||||
uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
|
||||
11
.github/workflows/test-backend.yml
vendored
11
.github/workflows/test-backend.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache-neo4j
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
with:
|
||||
path: /tmp/neo4j.tar
|
||||
key: ${{ github.run_id }}-backend-neo4j-cache
|
||||
@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache-backend
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
with:
|
||||
path: /tmp/backend.tar
|
||||
key: ${{ github.run_id }}-backend-cache
|
||||
@ -87,14 +87,14 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
|
||||
- name: Restore Neo4J cache
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
with:
|
||||
path: /tmp/neo4j.tar
|
||||
key: ${{ github.run_id }}-backend-neo4j-cache
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Restore Backend cache
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
with:
|
||||
path: /tmp/backend.tar
|
||||
key: ${{ github.run_id }}-backend-cache
|
||||
@ -112,7 +112,8 @@ jobs:
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: backend | docker compose
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps neo4j backend
|
||||
# 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 neo4j backend --build
|
||||
|
||||
- name: backend | Initialize Database
|
||||
run: docker compose exec -T backend yarn db:migrate init
|
||||
|
||||
8
.github/workflows/test-e2e.yml
vendored
8
.github/workflows/test-e2e.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
with:
|
||||
path: |
|
||||
/opt/cucumber-json-formatter
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
job: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
steps:
|
||||
- name: Restore cache
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
@ -77,7 +77,7 @@ jobs:
|
||||
docker load < /tmp/images/neo4j.tar
|
||||
docker load < /tmp/images/backend.tar
|
||||
docker load < /tmp/images/webapp.tar
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend --build
|
||||
sleep 90s
|
||||
|
||||
- name: Full stack tests | run tests
|
||||
@ -93,7 +93,7 @@ jobs:
|
||||
- name: Full stack tests | if tests failed, upload report
|
||||
id: e2e-report
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
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
|
||||
|
||||
7
.github/workflows/test-webapp.yml
vendored
7
.github/workflows/test-webapp.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
||||
docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar
|
||||
|
||||
- name: Cache docker image
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
with:
|
||||
path: /tmp/webapp.tar
|
||||
key: ${{ github.run_id }}-webapp-cache
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||
|
||||
- name: Restore webapp cache
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
|
||||
with:
|
||||
path: /tmp/webapp.tar
|
||||
key: ${{ github.run_id }}-webapp-cache
|
||||
@ -94,7 +94,8 @@ jobs:
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- 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
|
||||
run: docker compose exec -T webapp yarn test
|
||||
|
||||
2
.github/workflows/test.lint_pr.yml
vendored
2
.github/workflows/test.lint_pr.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@80c0371c57c5142ed6c844270bba1864bac8a4c6 # v5.5.3
|
||||
- uses: amannn/action-semantic-pull-request@04501d43b574e4c1d23c629ffe4dcec27acfdeff # v5.5.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,6 +15,7 @@ node_modules/
|
||||
cypress/videos
|
||||
cypress/screenshots/
|
||||
cypress.env.json
|
||||
deployment/configurations/
|
||||
|
||||
.vuepress/.cache/
|
||||
.vuepress/.temp/
|
||||
|
||||
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@ -0,0 +1 @@
|
||||
nodejs 20.12.1
|
||||
298
CHANGELOG.md
298
CHANGELOG.md
@ -4,8 +4,306 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [3.2.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.2.0...3.2.1)
|
||||
|
||||
- remove the requirement for non-existant job in publish workflow [`#8251`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8251)
|
||||
- removed dockerhub related stuff [`#8249`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8249)
|
||||
- feat(webapp): implement configurable custom button in header [`#8215`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8215)
|
||||
- feat(other): major improvement of deployment [`#7925`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7925)
|
||||
- Bump the metascraper group across 1 directory with 12 updates [`#8187`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8187)
|
||||
- Bump peter-evans/repository-dispatch from 39d2331fbbe4be56c4434ca745a23633155f9cdf to b0b38f73c8333be75d585a92b2c630a10d2a78f5 [`#8206`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8206)
|
||||
- Bump gaurav-nelson/github-action-markdown-link-check from 7d83e59a57f3c201c76eed3d33dff64ec4452d27 to 1b916f2cf6c36510a6059943104e3c42ce6c16bc [`#8178`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8178)
|
||||
- Bump xregexp from 4.3.0 to 5.1.2 in /webapp [`#8216`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8216)
|
||||
- Bump xregexp from 4.3.0 to 5.1.2 in /backend [`#8225`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8225)
|
||||
- Bump eslint-import-resolver-typescript from 3.7.0 to 3.8.3 in /backend [`#8222`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8222)
|
||||
- Bump nodemailer from 6.9.16 to 6.10.0 in /backend [`#8173`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8173)
|
||||
- Bump @faker-js/faker from 9.3.0 to 9.5.0 in /backend [`#8204`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8204)
|
||||
- Bump the babel group across 1 directory with 2 updates [`#8205`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8205)
|
||||
- Bump the babel group across 1 directory with 2 updates [`#8197`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8197)
|
||||
- Bump @types/node from 22.10.2 to 22.13.5 in /backend [`#8223`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8223)
|
||||
- Bump eslint-plugin-jest from 28.10.0 to 28.11.0 in /backend [`#8160`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8160)
|
||||
- Bump @faker-js/faker from 9.3.0 to 9.5.0 in /webapp [`#8211`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8211)
|
||||
- Bump @faker-js/faker from 9.3.0 to 9.5.0 [`#8196`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8196)
|
||||
- Bump eslint-config-prettier from 9.1.0 to 10.0.1 in /backend [`#8165`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8165)
|
||||
- Bump eslint-plugin-prettier from 5.2.1 to 5.2.3 in /backend [`#8170`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8170)
|
||||
- Bump prettier from 3.4.2 to 3.5.2 in /backend [`#8228`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8228)
|
||||
- Bump eslint-config-prettier from 6.15.0 to 10.0.1 in /webapp [`#8152`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8152)
|
||||
- Bump eslint-plugin-prettier from 5.2.1 to 5.2.3 in /webapp [`#8179`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8179)
|
||||
- Bump prettier from 3.4.2 to 3.5.2 in /webapp [`#8227`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8227)
|
||||
- Bump sass from 1.83.0 to 1.85.0 in /webapp [`#8207`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8207)
|
||||
- Bump the cypress group across 1 directory with 3 updates [`#8219`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8219)
|
||||
- Bump actions/setup-node from 4.1.0 to 4.2.0 [`#8188`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8188)
|
||||
- Bump actions/upload-artifact from 4.4.3 to 4.6.1 [`#8220`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8220)
|
||||
- Bump actions/cache from 4.1.2 to 4.2.1 [`#8221`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8221)
|
||||
- Bump typescript from 5.7.2 to 5.7.3 in /backend [`#8143`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8143)
|
||||
- Bump the cypress group across 1 directory with 5 updates [`#8175`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8175)
|
||||
- fix(frontend): hide invite button on mobile if disabled [`#8128`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8128)
|
||||
- Bump eslint-plugin-promise from 7.1.0 to 7.2.1 in /webapp [`#8096`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8096)
|
||||
- Bump eslint-plugin-vue from 9.31.0 to 9.32.0 in /webapp [`#8104`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8104)
|
||||
- Bump sass from 1.75.0 to 1.83.0 in /webapp [`#8121`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8121)
|
||||
- Bump prettier from 3.3.3 to 3.4.2 in /webapp [`#8100`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8100)
|
||||
- Bump @faker-js/faker from 9.2.0 to 9.3.0 in /webapp [`#8101`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8101)
|
||||
- Bump express from 4.21.1 to 4.21.2 in /webapp [`#8105`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8105)
|
||||
- Bump dotenv from 16.4.5 to 16.4.7 [`#8119`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8119)
|
||||
- Bump @faker-js/faker from 9.2.0 to 9.3.0 [`#8120`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8120)
|
||||
- Bump the cypress group across 1 directory with 2 updates [`#8134`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8134)
|
||||
- Bump sanitize-html from 2.13.1 to 2.14.0 in /backend [`#8132`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8132)
|
||||
- Bump eslint-import-resolver-typescript from 3.6.3 to 3.7.0 in /backend [`#8113`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8113)
|
||||
- Bump eslint-plugin-jest from 28.9.0 to 28.10.0 in /backend [`#8133`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8133)
|
||||
- Bump @babel/cli from 7.25.9 to 7.26.4 in /backend in the babel group [`#8108`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8108)
|
||||
- Bump express from 4.21.1 to 4.21.2 in /backend [`#8109`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8109)
|
||||
- Bump linkify-html from 4.1.4 to 4.2.0 in /backend [`#8116`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8116)
|
||||
- Bump @faker-js/faker from 9.2.0 to 9.3.0 in /backend [`#8115`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8115)
|
||||
- Bump graphql-redis-subscriptions from 2.2.1 to 2.7.0 in /backend [`#8122`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8122)
|
||||
- Bump @types/node from 22.9.3 to 22.10.2 in /backend [`#8124`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8124)
|
||||
- Bump nodemon from 3.1.7 to 3.1.9 in /backend [`#8123`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8123)
|
||||
- Bump linkifyjs from 4.1.4 to 4.2.0 in /backend [`#8112`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8112)
|
||||
- Bump prettier from 3.3.3 to 3.4.2 in /backend [`#8111`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8111)
|
||||
- Bump dotenv from 16.4.5 to 16.4.7 in /backend [`#8110`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8110)
|
||||
- Bump the cypress group with 2 updates [`#8092`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8092)
|
||||
- Bump @types/node from 22.9.0 to 22.9.3 in /backend [`#8093`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8093)
|
||||
- Bump typescript from 5.6.3 to 5.7.2 in /backend [`#8086`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8086)
|
||||
- Bump linkify-html from 4.1.3 to 4.1.4 in /backend [`#8085`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8085)
|
||||
- Bump linkifyjs from 4.1.3 to 4.1.4 in /backend [`#8088`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8088)
|
||||
- Bump eslint-plugin-vue from 9.30.0 to 9.31.0 in /webapp [`#8081`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8081)
|
||||
- Bump @badeball/cypress-cucumber-preprocessor from 21.0.2 to 21.0.3 in the cypress group [`#8083`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8083)
|
||||
- Bump amannn/action-semantic-pull-request from 80c0371c57c5142ed6c844270bba1864bac8a4c6 to 40166f00814508ec3201fc8595b393d451c8cd80 [`#8078`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8078)
|
||||
- Bump peter-evans/repository-dispatch from 28a02cc85a65462275a97039562642d3731c318c to 39d2331fbbe4be56c4434ca745a23633155f9cdf [`#8077`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8077)
|
||||
- Bump @faker-js/faker from 9.1.0 to 9.2.0 [`#8069`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8069)
|
||||
- Bump peter-evans/repository-dispatch from af19ba8f0cf25cd177db9cf116a43798bc7446c7 to 28a02cc85a65462275a97039562642d3731c318c [`#8067`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8067)
|
||||
- Bump cypress from 13.15.1 to 13.15.2 in the cypress group [`#8068`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8068)
|
||||
- Bump @faker-js/faker from 9.1.0 to 9.2.0 in /webapp [`#8074`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8074)
|
||||
- Bump eslint-plugin-jest from 28.8.3 to 28.9.0 in /backend [`#8073`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8073)
|
||||
- Bump aws-sdk from 2.1691.0 to 2.1692.0 in /backend [`#8072`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8072)
|
||||
- Bump @faker-js/faker from 9.1.0 to 9.2.0 in /backend [`#8071`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8071)
|
||||
- Bump @types/node from 22.8.7 to 22.9.0 in /backend [`#8070`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8070)
|
||||
- Bump @types/node from 22.8.4 to 22.8.7 in /backend [`#8066`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8066)
|
||||
- Bump peter-evans/repository-dispatch from cc7d8686e214c240128fe2680a5c1c5486ae19c4 to af19ba8f0cf25cd177db9cf116a43798bc7446c7 [`#8061`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8061)
|
||||
- Bump eslint-config-standard from 14.1.1 to 15.0.1 in /webapp [`#8063`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8063)
|
||||
- Bump eslint-plugin-promise from 4.3.1 to 7.1.0 in /webapp [`#8056`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8056)
|
||||
- Bump eslint-plugin-import from 2.20.2 to 2.31.0 in /webapp [`#8055`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8055)
|
||||
- Bump eslint-plugin-prettier from 5.1.3 to 5.2.1 in /webapp [`#8051`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8051)
|
||||
- Bump eslint-plugin-vue from 9.26.0 to 9.30.0 in /webapp [`#8057`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8057)
|
||||
- fix(frontend): adapt to different logo sizes, including fixes for Safari [`#8044`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8044)
|
||||
- refactor(other): dependabot: further package update regroupings [`#8045`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8045)
|
||||
- Bump @types/node from 22.8.1 to 22.8.4 in /backend [`#8048`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8048)
|
||||
- Bump the cypress group across 1 directory with 4 updates [`#8019`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8019)
|
||||
- refactor(maintenance): dependabot: disable frontend dependency checks [`#8041`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8041)
|
||||
- build(backend): update minor backend dependencies [`#8046`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8046)
|
||||
- refactor(other): adapt cypress config to cypress-cucumber-preprocessor update [`#8043`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8043)
|
||||
- Bump crazy-max/ghaction-github-pages from 08f571653184e9ff3d598bdda53ffd4ed00ed562 to fbf0a4fa4e00f45accd6cf3232368436ec06ed59 [`#8025`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8025)
|
||||
- fix(other): revert a change, which accedentally was merged [`#8039`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8039)
|
||||
- Bump nodemailer from 6.9.13 to 6.9.16 in /backend [`#8026`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8026)
|
||||
- Bump @faker-js/faker from 9.0.3 to 9.1.0 [`#8029`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8029)
|
||||
- Bump @faker-js/faker from 9.0.3 to 9.1.0 in /webapp [`#8036`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8036)
|
||||
- Bump @faker-js/faker from 9.0.3 to 9.1.0 in /backend [`#8031`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8031)
|
||||
- refactor(other): dependabot: degroups webapp package updates for babel, graphql, neo4j, and nuxt [`#8024`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8024)
|
||||
- Bump slug from 9.1.0 to 10.0.0 [`#7962`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7962)
|
||||
- Bump auto-changelog from 2.4.0 to 2.5.0 [`#7842`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7842)
|
||||
- Bump helmet from 7.1.0 to 8.0.0 in /backend [`#7916`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7916)
|
||||
- refactor(webapp): migrate sass devisions [`#7988`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7988)
|
||||
- Bump the vitest group across 1 directory with 2 updates [`#7963`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7963)
|
||||
- Bump actions/cache from 8469c94c6a180dfb41a1bd7e1b46ac557ea124f1 to 6849a6489940f00c2f30c0fb92c6274307ccb58a [`#7990`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7990)
|
||||
- Bump pinia-plugin-persistedstate from 4.1.1 to 4.1.2 in /frontend in the pinia group [`#8010`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8010)
|
||||
- Bump the babel group across 1 directory with 3 updates [`#8017`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8017)
|
||||
- Bump sass from 1.77.6 to 1.80.4 in /frontend [`#8023`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8023)
|
||||
- Bump @types/node from 22.7.7 to 22.8.1 in /backend [`#8021`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8021)
|
||||
- Bump the babel group across 1 directory with 6 updates [`#8022`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8022)
|
||||
- Bump @types/jest from 29.5.13 to 29.5.14 in /backend [`#8002`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8002)
|
||||
- Bump actions/setup-node from aca7b64a59c0063db8564e0ffdadd3887f1cbae5 to 39370e3970a6d050c480ffad4ff0ed4d3fdee5af [`#8020`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8020)
|
||||
- Bump actions/checkout from 163217dfcd28294438ea1c1c149cfaf66eec283e to 11bd71901bbe5b1630ceea73d27597364c9af683 [`#8018`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8018)
|
||||
- Bump peter-evans/repository-dispatch from e614736e88253e8888967eafadb9b75b237d52ba to cc7d8686e214c240128fe2680a5c1c5486ae19c4 [`#8016`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8016)
|
||||
- Bump the vuepress group across 1 directory with 4 updates [`#7999`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7999)
|
||||
- fix(webapp): change theme color to be brandable [`#8004`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8004)
|
||||
- fix(webapp): notification page has unbranded button [`#8001`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8001)
|
||||
- refactor(webapp): degroup sass packages in dependabot [`#7985`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7985)
|
||||
- refactor(webapp): degroup vue packages in dependabot [`#7984`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7984)
|
||||
- refactor(other): change set node version in readme files [`#7983`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7983)
|
||||
- Bump actions/cache from 81382a721fc89d96eca335d0c3ba33144b2baa9d to 8469c94c6a180dfb41a1bd7e1b46ac557ea124f1 [`#7927`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7927)
|
||||
- Bump @faker-js/faker from 9.0.0 to 9.0.3 [`#7885`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7885)
|
||||
- Bump sirv from 2.0.4 to 3.0.0 in /frontend [`#7942`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7942)
|
||||
- Bump the babel group across 1 directory with 3 updates [`#7944`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7944)
|
||||
- build(webapp): bump the babel group across 1 directory with 2 updates [`#7950`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7950)
|
||||
- Bump @types/node from 22.5.5 to 22.7.7 in /backend [`#7975`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7975)
|
||||
- Bump the babel group across 1 directory with 6 updates [`#7938`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7938)
|
||||
- Bump eslint-plugin-import from 2.30.0 to 2.31.0 in /backend [`#7915`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7915)
|
||||
- Bump sanitize-html from 2.13.0 to 2.13.1 in /backend [`#7917`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7917)
|
||||
- Bump @faker-js/faker from 9.0.0 to 9.0.3 in /backend [`#7882`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7882)
|
||||
- Bump nodemon from 3.1.0 to 3.1.7 in /backend [`#7872`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7872)
|
||||
- Bump actions/checkout from 6d193bf28034eafb982f37bd894289fe649468fc to 163217dfcd28294438ea1c1c149cfaf66eec283e [`#7972`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7972)
|
||||
- Bump express from 4.21.0 to 4.21.1 in /backend [`#7932`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7932)
|
||||
- Bump actions/setup-node from 97ca147735c170fb35096b39ef17a0fc5d9270ac to aca7b64a59c0063db8564e0ffdadd3887f1cbae5 [`#7889`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7889)
|
||||
- Bump typescript from 5.6.2 to 5.6.3 in /backend [`#7940`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7940)
|
||||
- fix(backend): fix geodata in mapbox-related resolver test [`#7976`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7976)
|
||||
- Bump the pinia group across 1 directory with 2 updates [`#7905`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7905)
|
||||
- Bump actions/upload-artifact from 4.4.0 to 4.4.3 [`#7930`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7930)
|
||||
- Bump express from 4.19.2 to 4.21.1 in /frontend [`#7933`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7933)
|
||||
- Bump express from 4.21.0 to 4.21.1 in /webapp [`#7949`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7949)
|
||||
- Bump the stylelint group across 1 directory with 2 updates [`#7937`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7937)
|
||||
- Bump @vue/compiler-sfc from 3.5.3 to 3.5.12 in /frontend [`#7941`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7941)
|
||||
- Bump peter-evans/repository-dispatch from c2fad29759e65af76987543a0aabefb8906d379a to e614736e88253e8888967eafadb9b75b237d52ba [`#7973`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7973)
|
||||
- Bump the storybook group across 1 directory with 8 updates [`#7970`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7970)
|
||||
- Bump @faker-js/faker from 9.0.0 to 9.0.3 in /webapp [`#7878`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7878)
|
||||
- Bump eslint from 8.57.0 to 8.57.1 in /backend [`#7870`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7870)
|
||||
- chore(other): adjust docs for deployment, neo4j cypher commands, and have new scripts [`#7848`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7848)
|
||||
- refactor(other): remove package wait-on [`#7819`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7819)
|
||||
- Bump crazy-max/ghaction-github-pages from cda5497acf90563d34489ed832a67c2c50353a16 to 08f571653184e9ff3d598bdda53ffd4ed00ed562 [`#7833`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7833)
|
||||
- Bump actions/setup-node from 1c7b2db92075f828bee89d7e19d33a911d15e7b3 to 97ca147735c170fb35096b39ef17a0fc5d9270ac [`#7834`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7834)
|
||||
- refactor(frontend): remove chromatic [`#7818`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7818)
|
||||
- Bump express from 4.19.2 to 4.21.0 in /webapp [`#7844`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7844)
|
||||
- Bump express from 4.19.2 to 4.21.0 in /backend [`#7840`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7840)
|
||||
- Bump @types/jest from 29.5.12 to 29.5.13 in /backend [`#7837`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7837)
|
||||
- Bump @types/node from 22.5.4 to 22.5.5 in /backend [`#7838`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7838)
|
||||
- Bump typescript from 5.4.5 to 5.6.2 in /backend [`#7839`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7839)
|
||||
- build(other): bump cheerio from 1.0.0-rc.12 to 1.0.0 in /backend [`#7709`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7709)
|
||||
- Bump @faker-js/faker from 8.4.1 to 9.0.0 [`#7791`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7791)
|
||||
- build(other): bump @faker-js/faker from 8.4.1 to 9.0.0 [`#7817`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7817)
|
||||
- Bump vike from 0.4.177 to 0.4.195 in /frontend [`#7820`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7820)
|
||||
- Bump apollo-client from 2.6.8 to 2.6.10 in /webapp in the apollo group [`#7692`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7692)
|
||||
- Bump date-fns from 3.3.1 to 3.6.0 [`#7139`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7139)
|
||||
- Bump @types/node from 20.12.7 to 22.5.4 in /backend [`#7812`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7812)
|
||||
- Bump slug from 9.0.0 to 9.1.0 [`#7457`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7457)
|
||||
- Bump the vitest group across 1 directory with 2 updates [`#7683`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7683)
|
||||
- Bump the stylelint group across 1 directory with 2 updates [`#7773`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7773)
|
||||
- Bump validator from 13.11.0 to 13.12.0 in /webapp [`#7393`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7393)
|
||||
- Bump peter-evans/repository-dispatch from 733a1daa12a9e7f9b219279836ac0190f48cf46d to c2fad29759e65af76987543a0aabefb8906d379a [`#7805`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7805)
|
||||
- Bump @vue/compiler-sfc from 3.4.30 to 3.5.3 in /frontend [`#7795`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7795)
|
||||
- Bump actions/checkout from 9a9194f87191a7e9055e3e9b95b8cfb13023bb08 to 6d193bf28034eafb982f37bd894289fe649468fc [`#7804`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7804)
|
||||
- Bump actions/cache from 40c3b67b2955d93d83b27ed164edd0756bc24049 to 81382a721fc89d96eca335d0c3ba33144b2baa9d [`#7781`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7781)
|
||||
- Bump actions/setup-node from 26961cf329f22f6837d5f54c3efd76b480300ace to 1c7b2db92075f828bee89d7e19d33a911d15e7b3 [`#7803`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7803)
|
||||
- Bump vue-i18n from 9.13.1 to 9.14.0 in /frontend [`#7728`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7728)
|
||||
- Bump eslint-import-resolver-typescript from 3.6.1 to 3.6.3 in /backend [`#7788`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7788)
|
||||
- Bump ts-jest from 29.1.2 to 29.2.5 in /backend in the typescript group across 1 directory [`#7758`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7758)
|
||||
- Bump the babel group across 1 directory with 6 updates [`#7787`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7787)
|
||||
- Bump eslint-plugin-jest from 28.2.0 to 28.8.3 in /backend [`#7814`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7814)
|
||||
- Bump eslint-plugin-import from 2.29.1 to 2.30.0 in /backend [`#7815`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7815)
|
||||
- Bump slug from 9.0.0 to 9.1.0 in /backend [`#7455`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7455)
|
||||
- Bump peter-evans/repository-dispatch from 1ebfb41781aa0fae446773941d0b3025198fc1a9 to 733a1daa12a9e7f9b219279836ac0190f48cf46d [`#7782`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7782)
|
||||
- Bump actions/upload-artifact from 4.3.5 to 4.4.0 [`#7780`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7780)
|
||||
- Bump prettier from 3.2.5 to 3.3.3 in /webapp [`#7629`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7629)
|
||||
- Bump prettier from 3.3.2 to 3.3.3 in /frontend [`#7641`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7641)
|
||||
- fix(workflow): fix typo in publishing workflow [`#7770`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7770)
|
||||
- fix(workflow): reset pkgdeps/git-tag-action to v3.0.0 in publish.yml [`#7769`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7769)
|
||||
- chore(other): remove obsolete version element from all docker yaml files [`#7564`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7564)
|
||||
- refactor(workflow): pin all github actions by commit hash [`#7701`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7701)
|
||||
- fix(webapp): fix maintenance dockerfiles image name [`#7768`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7768)
|
||||
- refactor(workflow): replace docker-compose by docker compose in workflow files [`#7762`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7762)
|
||||
- fix(backend): fix backend tests by changing mapbox location coordinates [`#7766`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7766)
|
||||
- Bump @vue/compiler-sfc from 3.4.29 to 3.4.30 in /frontend [`#7563`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7563)
|
||||
- Bump prettier from 3.2.5 to 3.3.2 in /frontend [`#7530`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7530)
|
||||
- chore(frontend): update frontend packages 2024 06 22 [`#7562`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7562)
|
||||
- Bump the typescript group across 1 directory with 4 updates [`#7487`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7487)
|
||||
- Bump chromatic from 11.3.2 to 11.5.0 in /frontend [`#7491`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7491)
|
||||
- Bump vike from 0.4.171 to 0.4.172 in /frontend [`#7489`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7489)
|
||||
- Bump the vite group in /frontend with 2 updates [`#7486`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7486)
|
||||
- Bump the vuepress group across 1 directory with 3 updates [`#7485`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7485)
|
||||
- Bump stylelint from 16.5.0 to 16.6.1 in /frontend in the stylelint group across 1 directory [`#7484`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7484)
|
||||
- refactor(other): group dependabot updates [`#7431`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7431)
|
||||
- chore(frontend): update packages 2024 05 12 [`#7401`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7401)
|
||||
- feat(frontend): implement ocelot color tokens in frontend with themes [`#7144`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7144)
|
||||
- Bump eslint-plugin-vue from 9.25.0 to 9.26.0 in /webapp [`#7399`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7399)
|
||||
- refactor(other): bump @faker-js/faker from 5.1.0 to 8.4.1 in /webapp and adapt repo-wide code to deprecation warnings [`#6999`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6999)
|
||||
- Bump @badeball/cypress-cucumber-preprocessor from 20.0.3 to 20.0.4 [`#7333`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7333)
|
||||
- Bump cypress from 13.7.3 to 13.8.1 [`#7334`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7334)
|
||||
- Bump @cucumber/cucumber from 10.4.0 to 10.6.0 [`#7335`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7335)
|
||||
- Bump cropperjs from 1.6.1 to 1.6.2 in /webapp [`#7307`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7307)
|
||||
- feat(frontend): add icon components as vuetify assets with icons of actual webapp [`#7129`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7129)
|
||||
- build(other): bump linkifyjs from 2.1.8 to 4.1.3 in /backend [`#7220`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7220)
|
||||
- Bump jsonwebtoken from 9.0.0 to 9.0.2 in /webapp [`#7219`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7219)
|
||||
- Bump uuid from 8.3.2 to 9.0.1 in /backend [`#7206`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7206)
|
||||
- Bump @faker-js/faker from 7.6.0 to 8.4.1 in /backend [`#7181`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7181)
|
||||
- Bump slug from 6.0.0 to 9.0.0 in /backend [`#7193`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7193)
|
||||
- Bump sass from 1.30.0 to 1.75.0 in /webapp [`#7269`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7269)
|
||||
- Bump express from 4.18.3 to 4.19.2 in /webapp [`#7185`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7185)
|
||||
- Bump nodemon from 2.0.2 to 3.1.0 in /backend [`#7171`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7171)
|
||||
- build(other): bump neo4j-driver from 4.4.7 to latest v4 patch available [`#7197`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7197)
|
||||
- Bump typescript from 4.9.5 to 5.4.5 in /backend [`#7273`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7273)
|
||||
- Bump validator from 13.0.0 to 13.11.0 in /webapp [`#7208`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7208)
|
||||
- Bump jest from 29.5.0 to 29.7.0 in /webapp [`#7195`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7195)
|
||||
- Bump eslint-plugin-vue from 9.24.0 to 9.25.0 in /webapp [`#7282`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7282)
|
||||
- Bump @typescript-eslint/parser from 5.60.0 to 5.62.0 in /backend [`#7188`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7188)
|
||||
- Bump eslint-plugin-security from 2.1.1 to 3.0.0 in /backend [`#7275`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7275)
|
||||
- Bump eslint-plugin-jest from 27.2.2 to 28.2.0 in /backend [`#7204`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7204)
|
||||
- Bump vue-advanced-chat from 2.0.10 to 2.0.11 in /webapp [`#7175`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7175)
|
||||
- chore(other): update docker image versions [`#7238`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7238)
|
||||
- Bump babel-jest from 29.5.0 to 29.7.0 in /webapp [`#7169`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7169)
|
||||
- Bump @babel/core from 7.23.7 to 7.24.4 in /webapp [`#7226`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7226)
|
||||
- Bump eslint from 8.43.0 to 8.57.0 in /backend [`#7174`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7174)
|
||||
- Bump @storybook/addon-a11y from 8.0.5 to 8.0.8 in /webapp [`#7268`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7268)
|
||||
- chore(frontend): add .vscode to gitignore in /frontend [`#7278`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7278)
|
||||
- Bump vue-i18n from 9.12.0 to 9.12.1 in /frontend [`#7280`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7280)
|
||||
- Bump eslint-plugin-vue from 9.24.1 to 9.25.0 in /frontend [`#7279`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7279)
|
||||
- chore(frontend): update frontend packages 2024 04 14 [`#7277`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7277)
|
||||
- Bump cypress from 13.7.2 to 13.7.3 [`#7241`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7241)
|
||||
- Bump aws-sdk from 2.1595.0 to 2.1599.0 in /backend [`#7274`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7274)
|
||||
- Bump @types/node from 20.12.5 to 20.12.7 in /backend [`#7276`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7276)
|
||||
- Bump aws-sdk from 2.1594.0 to 2.1595.0 in /backend [`#7236`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7236)
|
||||
- chore(backend): update packages [`#7235`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7235)
|
||||
- Bump @cucumber/cucumber from 10.3.1 to 10.4.0 [`#7163`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7163)
|
||||
- Bump @badeball/cypress-cucumber-preprocessor from 20.0.2 to 20.0.3 [`#7161`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7161)
|
||||
- Bump minimatch from 9.0.3 to 9.0.4 in /backend [`#7214`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7214)
|
||||
- Bump sanitize-html from 2.11.0 to 2.13.0 in /backend [`#7218`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7218)
|
||||
- Bump happy-dom from 14.7.0 to 14.7.1 in /frontend [`#7234`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7234)
|
||||
- chore(frontend): update boilerplate 07 04 [`#7233`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7233)
|
||||
- Bump helmet from 7.0.0 to 7.1.0 in /backend [`#7160`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7160)
|
||||
- build(other): bump vike from 0.4.163 to 0.4.168 in /frontend [`#7153`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7153)
|
||||
- refactor(other): fix dependabot pr limit problem [`#7159`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7159)
|
||||
- chore(other): set dependabot package update pr limit to none [`#7158`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7158)
|
||||
- chore(frontend): update frontend boilerplate 06 04 [`#7157`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7157)
|
||||
- Bump eslint-plugin-vue from 9.19.2 to 9.24.0 in /webapp [`#7147`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7147)
|
||||
- Bump @babel/preset-env from 7.24.0 to 7.24.4 [`#7150`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7150)
|
||||
- Bump cypress from 13.7.0 to 13.7.2 [`#7151`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7151)
|
||||
- Bump @storybook/addon-a11y from 6.3.6 to 8.0.5 in /webapp [`#7146`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7146)
|
||||
- Bump actions/cache from 4.0.1 to 4.0.2 [`#7133`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7133)
|
||||
- Bump multiple-cucumber-html-reporter from 3.6.1 to 3.6.2 [`#7138`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7138)
|
||||
- Bump dotenv from 16.4.4 to 16.4.5 [`#7137`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7137)
|
||||
- Bump express from 4.17.1 to 4.18.3 in /webapp [`#7115`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7115)
|
||||
- Bump slug from 8.2.3 to 9.0.0 [`#7126`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7126)
|
||||
- Bump vuepress-theme-hope from 2.0.0-rc.29 to 2.0.0-rc.31 [`#7124`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7124)
|
||||
- Bump migrate from 2.0.1 to 2.1.0 in /backend [`#7118`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7118)
|
||||
- Bump @badeball/cypress-cucumber-preprocessor from 20.0.1 to 20.0.2 [`#7122`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7122)
|
||||
- Bump cypress from 13.6.6 to 13.7.0 [`#7123`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7123)
|
||||
- fix(webapp): remove reported unused locale keys [`#7105`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7105)
|
||||
- build(other): bump minimatch from 3.0.4 to 9.0.3 in /backend [`#6511`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6511)
|
||||
- build(other): bump prettier from 2.7.1 to 3.2.5 in /webapp [`#6997`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6997)
|
||||
- build(other): bump prettier from 2.8.8 to 3.2.5 in /backend [`#7000`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7000)
|
||||
- Bump eslint-plugin-security from 1.7.1 to 2.1.1 in /backend [`#7041`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7041)
|
||||
- Bump vuepress-theme-hope from 2.0.0-rc.23 to 2.0.0-rc.29 [`#7047`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7047)
|
||||
- Bump cypress from 13.6.4 to 13.6.6 [`#7045`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7045)
|
||||
- Bump @babel/preset-env from 7.23.9 to 7.24.0 [`#7048`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7048)
|
||||
- Bump dorny/paths-filter from 3.0.1 to 3.0.2 [`#7049`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7049)
|
||||
- feat(webapp): normalize locale files [`#7038`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7038)
|
||||
- feat(webapp): update locales event placeholders [`#6793`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6793)
|
||||
- Bump date-fns from 2.30.0 to 3.3.1 [`#7037`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7037)
|
||||
- Bump @babel/register from 7.22.15 to 7.23.7 [`#6931`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6931)
|
||||
- Bump @babel/core from 7.23.7 to 7.24.0 [`#7034`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7034)
|
||||
- build(other): bump node-fetch from 2.6.1 to 2.7.0 in /backend [`#6960`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6960)
|
||||
- Bump peter-evans/repository-dispatch from 2 to 3 [`#6966`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6966)
|
||||
- Bump @faker-js/faker from 8.4.0 to 8.4.1 [`#7026`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7026)
|
||||
- Bump babel-jest from 25.2.6 to 29.7.0 in /backend [`#6961`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6961)
|
||||
- Bump actions/cache from 4.0.0 to 4.0.1 [`#7028`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7028)
|
||||
- chore(frontend): update frontend boilerplate 26 02 [`#7027`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7027)
|
||||
- fix(docker): docker rework [`#92`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/92)
|
||||
- chore(docu): update vuepress [`#91`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/91)
|
||||
- Bump dorny/paths-filter from 3.0.0 to 3.0.1 [`#82`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/82)
|
||||
- chore(frontend): update packages 04.02 [`#90`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/90)
|
||||
- chore(frontend): update frontend boilerplate [`#7017`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7017)
|
||||
- chore(frontend): update packages 19.02 [`#86`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/86)
|
||||
- refactor(other): set ocelot color for docs website [`#7014`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7014)
|
||||
- refactor(other): adapt docs website navbar and footer to ocelot.social website [`#7016`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7016)
|
||||
- refactor(other): replace deprecated testbadgelink in main readme file [`#7012`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7012)
|
||||
- Bump dotenv from 16.3.1 to 16.4.4 [`#7010`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7010)
|
||||
- Bump dorny/paths-filter from 3.0.0 to 3.0.1 [`#7006`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7006)
|
||||
- chore(other): update packages 08.02 [`#75`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/75)
|
||||
- update frontend packages [`fef8157`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/fef8157b0cdf9081678365e9a7f6339ef787423c)
|
||||
- update frontend packages [`dce59c1`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/dce59c11089e23f3ed97dfbfea39507e7c098c30)
|
||||
- Add converted SVG icons with properties 'fill' and 'stroke' on 'svg' tag [`b1a7e91`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/b1a7e916314a0d59a65d2cdb244e6117d796dda1)
|
||||
|
||||
#### [3.2.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.1.2...3.2.0)
|
||||
|
||||
> 14 February 2024
|
||||
|
||||
- chore(other): release v3.2.0 – redesign of newsfeed, fix event datetime utc problem etc. [`#7002`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/7002)
|
||||
- feat(webapp): implement config for `date-time` format [`#6985`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6985)
|
||||
- build(other): bump eslint-plugin-n from 15.7.0 to 16.6.2 in /backend [`#6962`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6962)
|
||||
- feat(other): frontend workflows & test suites [`#6987`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6987)
|
||||
|
||||
@ -186,6 +186,9 @@ $ cp .env.template .env
|
||||
|
||||
# in folder backend/
|
||||
$ cp .env.template .env
|
||||
|
||||
# in folder frontend/
|
||||
$ cp .env.template .env
|
||||
```
|
||||
|
||||
For Development:
|
||||
|
||||
221
backend/.eslintrc.cjs
Normal file
221
backend/.eslintrc.cjs
Normal file
@ -0,0 +1,221 @@
|
||||
// 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',
|
||||
],
|
||||
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', // TODO // 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', // 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',
|
||||
],
|
||||
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',
|
||||
},
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
// this is to properly reference the referenced project database without requirement of compiling it
|
||||
// eslint-disable-next-line camelcase
|
||||
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,219 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
// es6: true,
|
||||
node: true,
|
||||
},
|
||||
/* parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},*/
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['prettier', '@typescript-eslint' /*, 'import', 'n', 'promise'*/],
|
||||
extends: [
|
||||
'standard',
|
||||
// 'eslint:recommended',
|
||||
'plugin:prettier/recommended',
|
||||
// 'plugin:import/recommended',
|
||||
// 'plugin:import/typescript',
|
||||
// 'plugin:security/recommended',
|
||||
// 'plugin:@eslint-community/eslint-comments/recommended',
|
||||
],
|
||||
settings: {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
/* rules: {
|
||||
//'indent': [ 'error', 2 ],
|
||||
//'quotes': [ "error", "single"],
|
||||
// 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
> 'no-console': ['error'],
|
||||
> 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
> 'prettier/prettier': ['error'],
|
||||
}, */
|
||||
rules: {
|
||||
'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': 'error',
|
||||
// 'import/default': 'error',
|
||||
// 'import/named': 'error',
|
||||
// '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': 'error',
|
||||
// 'import/no-default-export': 'error',
|
||||
// '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/handle-callback-err': 'error',
|
||||
// 'n/no-callback-literal': 'error',
|
||||
// 'n/no-exports-assign': 'error',
|
||||
// 'n/no-extraneous-import': 'error',
|
||||
// 'n/no-extraneous-require': 'error',
|
||||
// 'n/no-hide-core-modules': 'error',
|
||||
// 'n/no-missing-import': 'off', // not compatible with typescript
|
||||
// 'n/no-missing-require': 'error',
|
||||
// 'n/no-new-require': 'error',
|
||||
// 'n/no-path-concat': 'error',
|
||||
// 'n/no-process-exit': 'error',
|
||||
// 'n/no-unpublished-bin': 'error',
|
||||
// 'n/no-unpublished-import': 'off', // TODO need to exclude seeds
|
||||
// 'n/no-unpublished-require': 'error',
|
||||
// 'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
|
||||
// 'n/no-unsupported-features/es-builtins': 'error',
|
||||
// 'n/no-unsupported-features/es-syntax': 'error',
|
||||
// 'n/no-unsupported-features/node-builtins': 'error',
|
||||
// 'n/process-exit-as-throw': 'error',
|
||||
// 'n/shebang': 'error',
|
||||
// 'n/callback-return': 'error',
|
||||
// 'n/exports-style': 'error',
|
||||
// 'n/file-extension-in-import': 'off',
|
||||
// 'n/global-require': 'error',
|
||||
// 'n/no-mixed-requires': 'error',
|
||||
// 'n/no-process-env': 'error',
|
||||
// 'n/no-restricted-import': 'error',
|
||||
// 'n/no-restricted-require': 'error',
|
||||
// 'n/no-sync': 'error',
|
||||
// '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-promises/dns': 'error',
|
||||
// 'n/prefer-promises/fs': 'error',
|
||||
// promise
|
||||
// 'promise/catch-or-return': 'error',
|
||||
// 'promise/no-return-wrap': 'error',
|
||||
// 'promise/param-names': 'error',
|
||||
// 'promise/always-return': 'error',
|
||||
// 'promise/no-native': 'off',
|
||||
// 'promise/no-nesting': 'warn',
|
||||
// 'promise/no-promise-in-callback': 'warn',
|
||||
// 'promise/no-callback-in-promise': 'warn',
|
||||
// 'promise/avoid-new': 'warn',
|
||||
// 'promise/no-new-statics': 'error',
|
||||
// 'promise/no-return-in-finally': 'warn',
|
||||
// 'promise/valid-params': 'warn',
|
||||
// 'promise/prefer-await-to-callbacks': 'error',
|
||||
// 'promise/no-multiple-resolved': 'error',
|
||||
// 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',
|
||||
],
|
||||
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',
|
||||
},
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
// this is to properly reference the referenced project database without requirement of compiling it
|
||||
// eslint-disable-next-line camelcase
|
||||
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -1,103 +1,42 @@
|
||||
##################################################################################
|
||||
# BASE (Is pushed to DockerHub for rebranding) ###################################
|
||||
##################################################################################
|
||||
FROM node:20.12.1-alpine3.19 AS base
|
||||
|
||||
# ENVs
|
||||
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||
ENV DOCKER_WORKDIR="/app"
|
||||
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
|
||||
ARG BBUILD_DATE="1970-01-01T00:00:00.00Z"
|
||||
ENV BUILD_DATE=$BBUILD_DATE
|
||||
## We cannot do $(yarn run version)-${BUILD_NUMBER} here so we default to 0.0.0-0
|
||||
ARG BBUILD_VERSION="0.0.0-0"
|
||||
ENV BUILD_VERSION=$BBUILD_VERSION
|
||||
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
|
||||
ARG BBUILD_COMMIT="0000000"
|
||||
ENV BUILD_COMMIT=$BBUILD_COMMIT
|
||||
## SET NODE_ENV
|
||||
ENV NODE_ENV="production"
|
||||
## App relevant Envs
|
||||
ENV PORT="4000"
|
||||
|
||||
# Labels
|
||||
LABEL org.label-schema.build-date="${BUILD_DATE}"
|
||||
LABEL org.label-schema.name="ocelot.social:backend"
|
||||
LABEL org.label-schema.description="Backend of the Social Network Software ocelot.social"
|
||||
LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md"
|
||||
LABEL org.label-schema.url="https://ocelot.social"
|
||||
LABEL org.label-schema.vcs-url="https://github.com/Ocelot-Social-Community/Ocelot-Social/tree/master/backend"
|
||||
LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
|
||||
LABEL org.label-schema.vendor="ocelot.social Community"
|
||||
LABEL org.label-schema.version="${BUILD_VERSION}"
|
||||
LABEL org.label-schema.schema-version="1.0"
|
||||
LABEL maintainer="devops@ocelot.social"
|
||||
|
||||
# Install Additional Software
|
||||
## install: git
|
||||
RUN apk --no-cache add git python3 make g++
|
||||
|
||||
# Settings
|
||||
## Expose Container Port
|
||||
ENV NODE_ENV="production"
|
||||
ENV PORT="4000"
|
||||
EXPOSE ${PORT}
|
||||
RUN apk --no-cache add git python3 make g++ bash
|
||||
RUN mkdir -p /app
|
||||
WORKDIR /app
|
||||
CMD ["/bin/bash", "-c", "yarn run start"]
|
||||
|
||||
## Workdir
|
||||
RUN mkdir -p ${DOCKER_WORKDIR}
|
||||
WORKDIR ${DOCKER_WORKDIR}
|
||||
|
||||
##################################################################################
|
||||
# DEVELOPMENT (Connected to the local environment, to reload on demand) ##########
|
||||
##################################################################################
|
||||
FROM base AS development
|
||||
CMD ["/bin/sh", "-c", "yarn install && yarn run dev"]
|
||||
|
||||
# We don't need to copy or build anything since we gonna bind to the
|
||||
# local filesystem which will need a rebuild anyway
|
||||
|
||||
# Run command
|
||||
# (for development we need to execute yarn install since the
|
||||
# node_modules are on another volume and need updating)
|
||||
CMD /bin/sh -c "yarn install && yarn run dev"
|
||||
|
||||
##################################################################################
|
||||
# CODE (Does contain all code files and is pushed to DockerHub for rebranding) ###
|
||||
##################################################################################
|
||||
FROM base AS code
|
||||
|
||||
# copy everything, but do not build.
|
||||
FROM base AS build
|
||||
COPY . .
|
||||
ONBUILD COPY ./branding/constants/ src/config/tmp
|
||||
ONBUILD RUN tools/replace-constants.sh
|
||||
ONBUILD COPY ./branding/email/ src/middleware/helpers/email/
|
||||
ONBUILD RUN yarn install --production=false --frozen-lockfile --non-interactive
|
||||
ONBUILD RUN yarn run build
|
||||
ONBUILD RUN mkdir /build
|
||||
ONBUILD RUN cp -r ./build /build
|
||||
ONBUILD RUN cp -r ./public /build/build
|
||||
ONBUILD RUN cp -r ./package.json yarn.lock /build
|
||||
ONBUILD RUN cd /build && yarn install --production=true --frozen-lockfile --non-interactive
|
||||
|
||||
##################################################################################
|
||||
# BUILD (Does contain all files and the compilate and is therefore bloated) ######
|
||||
##################################################################################
|
||||
FROM code AS build
|
||||
|
||||
# yarn install
|
||||
RUN yarn install --production=false --frozen-lockfile --non-interactive
|
||||
# yarn build
|
||||
RUN /bin/sh -c "yarn run build"
|
||||
|
||||
##################################################################################
|
||||
# TEST ###########################################################################
|
||||
##################################################################################
|
||||
FROM build AS test
|
||||
# required for the migrations
|
||||
# ONBUILD RUN cp -r ./src /src
|
||||
CMD ["/bin/bash", "-c", "yarn run dev"]
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "yarn run dev"
|
||||
FROM build AS production_build
|
||||
|
||||
##################################################################################
|
||||
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
|
||||
##################################################################################
|
||||
FROM base AS production
|
||||
|
||||
# Copy "binary"-files from build image
|
||||
COPY --from=build ${DOCKER_WORKDIR}/build ./build
|
||||
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
|
||||
# Copy static files
|
||||
# TODO - externalize the uploads so we can copy the whole folder
|
||||
COPY --from=build ${DOCKER_WORKDIR}/public/img/ ./public/img/
|
||||
COPY --from=build ${DOCKER_WORKDIR}/public/providers.json ./public/providers.json
|
||||
# Copy package.json for script definitions (lock file should not be needed)
|
||||
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "yarn run start"
|
||||
COPY --from=production_build /build .
|
||||
|
||||
@ -6,12 +6,12 @@ Run the following command to install everything through docker.
|
||||
|
||||
The installation takes a bit longer on the first pass or on rebuild ...
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# in main folder
|
||||
$ docker-compose up
|
||||
$ docker compose up
|
||||
# or
|
||||
# rebuild the containers for a cleanup
|
||||
$ docker-compose up --build
|
||||
$ docker compose up --build
|
||||
```
|
||||
|
||||
Wait a little until your backend is up and running at [http://localhost:4000/](http://localhost:4000/).
|
||||
@ -26,7 +26,7 @@ some known problems with more recent node versions). You can use the
|
||||
[node version manager](https://github.com/nvm-sh/nvm) `nvm` to switch
|
||||
between different local Node versions:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# install Node
|
||||
$ cd backend
|
||||
$ nvm install v20.12.1
|
||||
@ -35,7 +35,7 @@ $ nvm use v20.12.1
|
||||
|
||||
Install node dependencies with [yarn](https://yarnpkg.com/en/):
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# in main folder
|
||||
$ cd backend
|
||||
$ yarn install
|
||||
@ -47,7 +47,7 @@ $ nvm use && yarn
|
||||
|
||||
Copy Environment Variables:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# in backend/
|
||||
$ cp .env.template .env
|
||||
```
|
||||
@ -57,14 +57,14 @@ a [local Neo4J](http://localhost:7474) instance is up and running.
|
||||
|
||||
Start the backend for development with:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# in backend/
|
||||
$ yarn run dev
|
||||
```
|
||||
|
||||
or start the backend in production environment with:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# in backend/
|
||||
$ yarn run start
|
||||
```
|
||||
@ -79,154 +79,120 @@ More details about our GraphQL playground and how to use it with ocelot.social c
|
||||
|
||||

|
||||
|
||||
### Database Indexes and Constraints
|
||||
## Database
|
||||
|
||||
Database indexes and constraints need to be created and upgraded when the database and the backend are running:
|
||||
A fresh database needs to be initialized and migrated.
|
||||
|
||||
::: tabs
|
||||
@tab:active Docker
|
||||
|
||||
```bash
|
||||
# in main folder while docker-compose is running
|
||||
$ docker exec backend yarn run db:migrate init
|
||||
|
||||
# only once: init admin user and create indexes and constraints in Neo4j database
|
||||
# for development
|
||||
$ docker compose exec backend yarn prod:migrate init
|
||||
# in production mode use command
|
||||
$ docker compose exec backend /bin/sh -c "yarn prod:migrate init"
|
||||
```sh
|
||||
# in folder backend while database is running
|
||||
yarn db:migrate init
|
||||
# for docker environments:
|
||||
docker exec backend yarn db:migrate init
|
||||
# for docker production:
|
||||
docker exec backend yarn prod:migrate init
|
||||
```
|
||||
|
||||
```bash
|
||||
# in main folder with docker compose running
|
||||
$ docker exec backend yarn run db:migrate up
|
||||
```sh
|
||||
# in backend with database running (In docker or local)
|
||||
yarn db:migrate up
|
||||
|
||||
# for docker development:
|
||||
docker exec backend yarn db:migrate up
|
||||
# for docker production
|
||||
docker exec backend yarn prod:migrate up
|
||||
```
|
||||
|
||||
@tab Without Docker
|
||||
### Optional Data
|
||||
|
||||
```bash
|
||||
# in folder backend/ while database is running
|
||||
# make sure your database is running on http://localhost:7474/browser/
|
||||
yarn run db:migrate init
|
||||
You can seed some optional data into the database.
|
||||
|
||||
To create the default admin <admin@example.org> with password `1234` use:
|
||||
|
||||
```sh
|
||||
# in backend with database running (In docker or local)
|
||||
yarn db:data:admin
|
||||
```
|
||||
|
||||
```bash
|
||||
# in backend/ with database running (In docker or local)
|
||||
yarn run db:migrate up
|
||||
When using `CATEGORIES_ACTIVE=true` you also want to seed the categories with:
|
||||
|
||||
```sh
|
||||
# in backend with database running (In docker or local)
|
||||
yarn db:data:categories
|
||||
```
|
||||
|
||||
:::
|
||||
### Seed Data
|
||||
|
||||
#### Seed Database
|
||||
For a predefined set of test data you can seed the database with:
|
||||
|
||||
If you want your backend to return anything else than an empty response, you
|
||||
need to seed your database:
|
||||
```sh
|
||||
# in backend with database running (In docker or local)
|
||||
yarn db:seed
|
||||
|
||||
::: tabs
|
||||
@tab:active Docker
|
||||
|
||||
In another terminal run:
|
||||
|
||||
```bash
|
||||
# in main folder while docker-compose is running
|
||||
$ docker exec backend yarn run db:seed
|
||||
# for docker
|
||||
docker exec backend yarn db:seed
|
||||
```
|
||||
|
||||
To reset the database run:
|
||||
### Reset Data
|
||||
|
||||
```bash
|
||||
# in main folder while docker-compose is running
|
||||
$ docker exec backend yarn run db:reset
|
||||
In order to reset the database you can run:
|
||||
|
||||
```sh
|
||||
# in backend with database running (In docker or local)
|
||||
yarn db:reset
|
||||
# or deleting the migrations as well
|
||||
yarn db:reset:withmigrations
|
||||
|
||||
# for docker
|
||||
docker exec backend yarn db:reset
|
||||
# or deleting the migrations as well
|
||||
docker exec backend yarn db:reset:withmigrations
|
||||
# you could also wipe out your neo4j database and delete all volumes with:
|
||||
$ docker-compose down -v
|
||||
# if container is not running, run this command to set up your database indexes and constraints
|
||||
$ docker exec backend yarn run db:migrate init
|
||||
# And then upgrade the indexes and const
|
||||
$ docker exec backend yarn run db:migrate up
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
@tab Without Docker
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
# in backend/ while database is running
|
||||
$ yarn run db:seed
|
||||
```
|
||||
|
||||
To reset the database run:
|
||||
|
||||
```bash
|
||||
# in backend/ while database is running
|
||||
$ yarn run db:reset
|
||||
```
|
||||
|
||||
:::
|
||||
> Note: This just deletes the data and not the constraints, hence you do not need to rerun `yarn db:migrate init` or `yarn db:migrate up`.
|
||||
|
||||
### Data migrations
|
||||
|
||||
Although Neo4J is schema-less,you might find yourself in a situation in which
|
||||
you have to migrate your data e.g. because your data modeling has changed.
|
||||
|
||||
::: tabs
|
||||
@tab:active Docker
|
||||
|
||||
Generate a data migration file:
|
||||
|
||||
```bash
|
||||
# in main folder while docker-compose is running
|
||||
$ docker-compose exec backend yarn run db:migrate:create your_data_migration
|
||||
# Edit the file in ./src/db/migrations/
|
||||
```
|
||||
|
||||
To run the migration:
|
||||
|
||||
```bash
|
||||
# in main folder while docker-compose is running
|
||||
$ docker exec backend yarn run db:migrate up
|
||||
```
|
||||
|
||||
@tab Without Docker
|
||||
|
||||
Generate a data migration file:
|
||||
|
||||
```bash
|
||||
# in backend/
|
||||
```sh
|
||||
# in backend
|
||||
$ yarn run db:migrate:create your_data_migration
|
||||
# Edit the file in ./src/db/migrations/
|
||||
|
||||
# for docker
|
||||
# in main folder while docker compose is running
|
||||
$ docker compose exec backend yarn run db:migrate:create your_data_migration
|
||||
# Edit the file in ./src/db/migrations/
|
||||
```
|
||||
|
||||
To run the migration:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# in backend/ while database is running
|
||||
$ yarn run db:migrate up
|
||||
```
|
||||
|
||||
:::
|
||||
# for docker
|
||||
# in main folder while docker compose is running
|
||||
$ docker exec backend yarn run db:migrate up
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
**Beware**: We have no multiple database setup at the moment. We clean the
|
||||
database after each test, running the tests will wipe out all your data!
|
||||
|
||||
::: tabs
|
||||
@tab:active Docker
|
||||
|
||||
Run the unit tests:
|
||||
|
||||
```bash
|
||||
# in main folder while docker-compose is running
|
||||
$ docker exec backend yarn run test
|
||||
```
|
||||
|
||||
@tab Without Docker
|
||||
|
||||
Run the unit tests:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
# in backend/ while database is running
|
||||
$ yarn run test
|
||||
```
|
||||
|
||||
:::
|
||||
# for docker
|
||||
# in main folder while docker compose is running
|
||||
$ docker exec backend yarn run test
|
||||
```
|
||||
|
||||
26
backend/jest.config.cjs
Normal file
26
backend/jest.config.cjs
Normal file
@ -0,0 +1,26 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
const { pathsToModuleNameMapper } = require('ts-jest')
|
||||
const requireJSON5 = require('require-json5')
|
||||
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>/' }),
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
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']
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-backend",
|
||||
"version": "3.2.0",
|
||||
"version": "3.2.1",
|
||||
"description": "GraphQL Backend for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
@ -8,26 +8,27 @@
|
||||
"private": false,
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"__migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations",
|
||||
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js",
|
||||
"start": "node build/src/",
|
||||
"build": "tsc && ./scripts/build.copy.files.sh",
|
||||
"dev": "nodemon --exec ts-node src/ -e js,ts,gql",
|
||||
"build": "tsc && tsc-alias && ./scripts/build.copy.files.sh",
|
||||
"dev": "nodemon --exec ts-node --require tsconfig-paths/register src/ -e js,ts,gql",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,ts,gql",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts ./src",
|
||||
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"db:clean": "ts-node src/db/clean.ts",
|
||||
"db:reset": "yarn run db:clean",
|
||||
"db:seed": "ts-node src/db/seed.ts",
|
||||
"db:migrate": "yarn run __migrate --store ./src/db/migrate/store.ts",
|
||||
"db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create"
|
||||
"db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts",
|
||||
"db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts",
|
||||
"db:seed": "ts-node --require tsconfig-paths/register src/db/seed.ts",
|
||||
"db:data:admin": "ts-node --require tsconfig-paths/register src/db/admin.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:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create",
|
||||
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/cli": "~7.25.9",
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/cli": "~7.27.0",
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/node": "~7.26.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.25.9",
|
||||
"@babel/preset-env": "~7.26.0",
|
||||
"@babel/preset-env": "~7.26.9",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@sentry/node": "^5.15.4",
|
||||
"apollo-cache-inmemory": "~1.6.6",
|
||||
@ -36,92 +37,102 @@
|
||||
"apollo-link-http": "~1.5.17",
|
||||
"apollo-server": "~2.14.2",
|
||||
"apollo-server-express": "^2.14.2",
|
||||
"aws-sdk": "^2.1599.0",
|
||||
"aws-sdk": "^2.1692.0",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.1.0",
|
||||
"babel-jest": "~29.7.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"body-parser": "^1.20.3",
|
||||
"cheerio": "~1.0.0",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~7.0.3",
|
||||
"dotenv": "~16.4.5",
|
||||
"express": "^4.21.1",
|
||||
"dotenv": "~16.4.7",
|
||||
"express": "^5.1.0",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-middleware": "~4.0.2",
|
||||
"graphql-middleware-sentry": "^3.2.1",
|
||||
"graphql-redis-subscriptions": "^2.2.1",
|
||||
"graphql-redis-subscriptions": "^2.7.0",
|
||||
"graphql-shield": "~7.2.2",
|
||||
"graphql-subscriptions": "^1.1.0",
|
||||
"graphql-tag": "~2.10.3",
|
||||
"helmet": "~8.0.0",
|
||||
"graphql-upload": "^11.0.0",
|
||||
"helmet": "~8.1.0",
|
||||
"ioredis": "^4.16.1",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"languagedetect": "^2.0.0",
|
||||
"linkifyjs": "^4.1.3",
|
||||
"linkify-html": "^4.1.3",
|
||||
"linkify-html": "^4.2.0",
|
||||
"linkifyjs": "^4.2.0",
|
||||
"lodash": "~4.17.21",
|
||||
"merge-graphql-schemas": "^1.7.8",
|
||||
"metascraper": "^5.33.5",
|
||||
"metascraper-author": "^5.33.5",
|
||||
"metascraper-date": "^5.33.5",
|
||||
"metascraper-description": "^5.33.5",
|
||||
"metascraper-image": "^5.33.5",
|
||||
"metascraper-lang": "^5.33.5",
|
||||
"metascraper": "^5.46.11",
|
||||
"metascraper-author": "^5.46.11",
|
||||
"metascraper-date": "^5.46.11",
|
||||
"metascraper-description": "^5.46.11",
|
||||
"metascraper-image": "^5.46.11",
|
||||
"metascraper-lang": "^5.46.11",
|
||||
"metascraper-lang-detector": "^4.10.2",
|
||||
"metascraper-logo": "^5.33.5",
|
||||
"metascraper-publisher": "^5.33.5",
|
||||
"metascraper-logo": "^5.46.11",
|
||||
"metascraper-publisher": "^5.46.11",
|
||||
"metascraper-soundcloud": "^5.34.4",
|
||||
"metascraper-title": "^5.34.7",
|
||||
"metascraper-url": "^5.34.2",
|
||||
"metascraper-video": "^5.33.5",
|
||||
"metascraper-youtube": "^5.33.5",
|
||||
"metascraper-title": "^5.46.11",
|
||||
"metascraper-url": "^5.46.11",
|
||||
"metascraper-video": "^5.46.11",
|
||||
"metascraper-youtube": "^5.46.11",
|
||||
"migrate": "^2.1.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"mime-types": "^3.0.1",
|
||||
"minimatch": "^9.0.4",
|
||||
"mustache": "^4.2.0",
|
||||
"neo4j-driver": "^4.4.11",
|
||||
"neo4j-graphql-js": "^2.11.5",
|
||||
"neode": "^0.4.9",
|
||||
"node-fetch": "^2.7.0",
|
||||
"nodemailer": "^6.9.16",
|
||||
"nodemailer": "^6.10.0",
|
||||
"nodemailer-html-to-text": "^3.2.0",
|
||||
"request": "~2.88.2",
|
||||
"sanitize-html": "~2.13.1",
|
||||
"sanitize-html": "~2.15.0",
|
||||
"slug": "~9.1.0",
|
||||
"subscriptions-transport-ws": "^0.9.19",
|
||||
"trunc-html": "~1.1.2",
|
||||
"uuid": "~9.0.1",
|
||||
"validator": "^13.11.0",
|
||||
"xregexp": "^4.3.0"
|
||||
"validator": "^13.15.0",
|
||||
"xregexp": "^5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "9.1.0",
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
|
||||
"@faker-js/faker": "9.6.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/node": "^22.14.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"apollo-server-testing": "~2.11.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-import-resolver-typescript": "^4.3.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.8.3",
|
||||
"eslint-plugin-n": "^16.6.2",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-security": "^3.0.0",
|
||||
"eslint-plugin-jest": "^28.11.0",
|
||||
"eslint-plugin-n": "^17.17.0",
|
||||
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
"eslint-plugin-security": "^3.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "~3.1.7",
|
||||
"prettier": "^3.2.5",
|
||||
"nodemon": "~3.1.9",
|
||||
"prettier": "^3.5.3",
|
||||
"require-json5": "^1.3.0",
|
||||
"rosie": "^2.1.1",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-jest": "^29.3.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.6.3"
|
||||
"tsc-alias": "^1.8.14",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/**/fs-capacitor": "^6.2.0",
|
||||
"**/graphql-upload": "^11.0.0",
|
||||
"nan": "2.17.0"
|
||||
"**/graphql-upload": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.12.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
# public
|
||||
cp -r public/ build/public/
|
||||
|
||||
# html files
|
||||
mkdir -p build/src/middleware/helpers/email/templates/
|
||||
cp -r src/middleware/helpers/email/templates/*.html build/src/middleware/helpers/email/templates/
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import dotenv from 'dotenv'
|
||||
/* eslint-disable n/no-process-env */
|
||||
/* eslint-disable n/no-unpublished-require */
|
||||
/* eslint-disable n/no-missing-require */
|
||||
import { config } from 'dotenv'
|
||||
|
||||
import emails from './emails'
|
||||
import metadata from './metadata'
|
||||
|
||||
// Load env file
|
||||
if (require.resolve) {
|
||||
try {
|
||||
dotenv.config({ path: require.resolve('../../.env') })
|
||||
config({ path: require.resolve('../../.env') })
|
||||
} catch (error) {
|
||||
// This error is thrown when the .env is not found
|
||||
if (error.code !== 'MODULE_NOT_FOUND') {
|
||||
|
||||
50
backend/src/db/admin.ts
Normal file
50
backend/src/db/admin.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { getDriver } from './neo4j'
|
||||
|
||||
const defaultAdmin = {
|
||||
email: 'admin@example.org',
|
||||
password: hashSync('1234', 10),
|
||||
name: 'admin',
|
||||
id: uuid(),
|
||||
slug: 'admin',
|
||||
}
|
||||
|
||||
const createDefaultAdminUser = async () => {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const createAdminTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
txc.run(
|
||||
`MERGE (e:EmailAddress {
|
||||
email: "${defaultAdmin.email}",
|
||||
createdAt: toString(datetime())
|
||||
})-[:BELONGS_TO]->(u:User {
|
||||
name: "${defaultAdmin.name}",
|
||||
encryptedPassword: "${defaultAdmin.password}",
|
||||
role: "admin",
|
||||
id: "${defaultAdmin.id}",
|
||||
slug: "${defaultAdmin.slug}",
|
||||
createdAt: toString(datetime()),
|
||||
allowEmbedIframes: false,
|
||||
showShoutsPublicly: false,
|
||||
deleted: false,
|
||||
disabled: false
|
||||
})-[:PRIMARY_EMAIL]->(e)`,
|
||||
)
|
||||
})
|
||||
try {
|
||||
await createAdminTxResultPromise
|
||||
console.log('Successfully created default admin user!') // eslint-disable-line no-console
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
} finally {
|
||||
session.close()
|
||||
driver.close()
|
||||
}
|
||||
}
|
||||
|
||||
;(async function () {
|
||||
await createDefaultAdminUser()
|
||||
})()
|
||||
36
backend/src/db/categories.ts
Normal file
36
backend/src/db/categories.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { categories } from '@constants/categories'
|
||||
|
||||
import { getDriver } from './neo4j'
|
||||
|
||||
const createCategories = async () => {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const createCategoriesTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
categories.forEach(({ icon, name }, index) => {
|
||||
const id = `cat${index + 1}`
|
||||
txc.run(
|
||||
`MERGE (c:Category {
|
||||
icon: "${icon}",
|
||||
slug: "${name}",
|
||||
name: "${name}",
|
||||
id: "${id}",
|
||||
createdAt: toString(datetime())
|
||||
})`,
|
||||
)
|
||||
})
|
||||
})
|
||||
try {
|
||||
await createCategoriesTxResultPromise
|
||||
console.log('Successfully created categories!') // eslint-disable-line no-console
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console
|
||||
} finally {
|
||||
session.close()
|
||||
driver.close()
|
||||
}
|
||||
}
|
||||
|
||||
;(async function () {
|
||||
await createCategories()
|
||||
})()
|
||||
@ -1,2 +1,7 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
// eslint-disable-next-line n/no-unpublished-require
|
||||
const tsNode = require('ts-node')
|
||||
// eslint-disable-next-line import/no-unassigned-import, import/no-extraneous-dependencies, n/no-unpublished-require
|
||||
require('tsconfig-paths/register')
|
||||
|
||||
module.exports = tsNode.register
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import slugify from 'slug'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import { Factory } from 'rosie'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import slugify from 'slug'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import generateInviteCode from '@schema/resolvers/helpers/generateInviteCode'
|
||||
|
||||
import { getDriver, getNeode } from './neo4j'
|
||||
import CONFIG from '../config/index'
|
||||
import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -15,18 +17,19 @@ const uniqueImageUrl = (imageUrl) => {
|
||||
return newUrl.toString()
|
||||
}
|
||||
|
||||
export const cleanDatabase = async (options: any = {}) => {
|
||||
const { driver = getDriver() } = options
|
||||
export const cleanDatabase = async ({ withMigrations } = { withMigrations: false }) => {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
|
||||
const clean = `
|
||||
MATCH (everything)
|
||||
${withMigrations ? '' : "WHERE NOT 'Migration' IN labels(everything)"}
|
||||
DETACH DELETE everything
|
||||
`
|
||||
|
||||
try {
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(
|
||||
`
|
||||
MATCH (everything)
|
||||
WHERE NOT 'Migration' IN labels(everything)
|
||||
DETACH DELETE everything
|
||||
`,
|
||||
)
|
||||
return transaction.run(clean)
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
@ -70,7 +73,6 @@ Factory.define('basicUser')
|
||||
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
|
||||
allowEmbedIframes: false,
|
||||
showShoutsPublicly: false,
|
||||
sendNotificationEmails: true,
|
||||
locale: 'en',
|
||||
})
|
||||
.attr('slug', ['slug', 'name'], (slug, name) => {
|
||||
@ -173,6 +175,7 @@ Factory.define('post')
|
||||
])
|
||||
await Promise.all([
|
||||
post.relateTo(author, 'author'),
|
||||
post.relateTo(author, 'observes'),
|
||||
// Promise.all(categories.map((c) => c.relateTo(post, 'post'))),
|
||||
Promise.all(tags.map((t) => t.relateTo(post, 'post'))),
|
||||
])
|
||||
@ -208,7 +211,11 @@ Factory.define('comment')
|
||||
options.author,
|
||||
options.post,
|
||||
])
|
||||
await Promise.all([comment.relateTo(author, 'author'), comment.relateTo(post, 'post')])
|
||||
await Promise.all([
|
||||
comment.relateTo(author, 'author'),
|
||||
comment.relateTo(post, 'post'),
|
||||
post.relateTo(author, 'observes'),
|
||||
])
|
||||
return comment
|
||||
})
|
||||
|
||||
|
||||
@ -1,104 +1,45 @@
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { categories } from '../../constants/categories'
|
||||
import CONFIG from '../../config'
|
||||
|
||||
const defaultAdmin = {
|
||||
email: 'admin@example.org',
|
||||
password: hashSync('1234', 10),
|
||||
name: 'admin',
|
||||
id: uuid(),
|
||||
slug: 'admin',
|
||||
}
|
||||
|
||||
const createCategories = async (session) => {
|
||||
const createCategoriesTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
categories.forEach(({ icon, name }, index) => {
|
||||
const id = `cat${index + 1}`
|
||||
txc.run(
|
||||
`MERGE (c:Category {
|
||||
icon: "${icon}",
|
||||
slug: "${name}",
|
||||
name: "${name}",
|
||||
id: "${id}",
|
||||
createdAt: toString(datetime())
|
||||
})`,
|
||||
)
|
||||
})
|
||||
})
|
||||
try {
|
||||
await createCategoriesTxResultPromise
|
||||
console.log('Successfully created categories!') // eslint-disable-line no-console
|
||||
} catch (error) {
|
||||
console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
const createDefaultAdminUser = async (session) => {
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const result = await txc.run('MATCH (user:User) RETURN count(user) AS userCount')
|
||||
return result.records.map((r) => r.get('userCount'))
|
||||
})
|
||||
let createAdmin = false
|
||||
try {
|
||||
const userCount = parseInt(String(await readTxResultPromise))
|
||||
if (userCount === 0) createAdmin = true
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
}
|
||||
if (createAdmin) {
|
||||
const createAdminTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
txc.run(
|
||||
`MERGE (e:EmailAddress {
|
||||
email: "${defaultAdmin.email}",
|
||||
createdAt: toString(datetime())
|
||||
})-[:BELONGS_TO]->(u:User {
|
||||
name: "${defaultAdmin.name}",
|
||||
encryptedPassword: "${defaultAdmin.password}",
|
||||
role: "admin",
|
||||
id: "${defaultAdmin.id}",
|
||||
slug: "${defaultAdmin.slug}",
|
||||
createdAt: toString(datetime()),
|
||||
allowEmbedIframes: false,
|
||||
showShoutsPublicly: false,
|
||||
sendNotificationEmails: true,
|
||||
deleted: false,
|
||||
disabled: false
|
||||
})-[:PRIMARY_EMAIL]->(e)`,
|
||||
)
|
||||
})
|
||||
try {
|
||||
await createAdminTxResultPromise
|
||||
console.log('Successfully created default admin user!') // eslint-disable-line no-console
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
}
|
||||
import { getDriver, getNeode } from '@db/neo4j'
|
||||
|
||||
class Store {
|
||||
async init(next) {
|
||||
async init(errFn) {
|
||||
const neode = getNeode()
|
||||
const { driver } = neode
|
||||
const session = driver.session()
|
||||
await createDefaultAdminUser(session)
|
||||
if (CONFIG.CATEGORIES_ACTIVE) await createCategories(session)
|
||||
const writeTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices and constraints
|
||||
const session = neode.driver.session()
|
||||
const txFreshIndicesConstrains = session.writeTransaction(async (txc) => {
|
||||
// drop all indices and constraints
|
||||
await txc.run('CALL apoc.schema.assert({},{},true)')
|
||||
/*
|
||||
#############################################
|
||||
# ADD YOUR CUSTOM INDICES & CONSTRAINS HERE #
|
||||
#############################################
|
||||
*/
|
||||
// Search indexes (also part of migration 20230320130345-fulltext-search-indexes)
|
||||
await txc.run(
|
||||
`CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"])`,
|
||||
)
|
||||
await txc.run(
|
||||
`CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"])`,
|
||||
)
|
||||
await txc.run(`CALL db.index.fulltext.createNodeIndex("tag_fulltext_search",["Tag"],["id"])`) // also part of migration 20200207080200-fulltext_index_for_tags
|
||||
// Search indexes (also part of migration 20220803060819-create_fulltext_indices_and_unique_keys_for_groups)
|
||||
await txc.run(`
|
||||
CALL db.index.fulltext.createNodeIndex("group_fulltext_search",["Group"],["name", "slug", "about", "description"])
|
||||
`)
|
||||
})
|
||||
try {
|
||||
await writeTxResultPromise
|
||||
// Due to limitations of neode in combination with the limitations of the community version of neo4j
|
||||
// we need to have all constraints and indexes defined here. They can not be properly migrated
|
||||
await txFreshIndicesConstrains
|
||||
|
||||
await getNeode().schema.install()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Successfully created database indices and constraints!')
|
||||
next()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error, null)
|
||||
errFn(error)
|
||||
} finally {
|
||||
session.close()
|
||||
driver.close()
|
||||
neode.driver.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,6 +63,7 @@ class Store {
|
||||
}
|
||||
const [{ title: lastRun }] = migrations
|
||||
next(null, { lastRun, migrations })
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error)
|
||||
@ -157,6 +99,7 @@ class Store {
|
||||
try {
|
||||
await writeTxResultPromise
|
||||
next()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error)
|
||||
@ -166,4 +109,4 @@ class Store {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Store
|
||||
export default Store
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
import { throwError, concat } from 'rxjs'
|
||||
import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
|
||||
import { getDriver } from '../neo4j'
|
||||
import normalizeEmail from '../../schema/resolvers//helpers/normalizeEmail'
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
import normalizeEmail from '@schema/resolvers/helpers/normalizeEmail'
|
||||
|
||||
export const description = `
|
||||
This migration merges duplicate :User and :EmailAddress nodes. It became
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
import { throwError, concat } from 'rxjs'
|
||||
import { flatMap, mergeMap, map, catchError } from 'rxjs/operators'
|
||||
import { getDriver } from '../neo4j'
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
This migration merges duplicate :Location nodes. It became
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
This migration creates a MUTED relationship between two edges(:User) that have a pre-existing BLOCKED relationship.
|
||||
@ -21,6 +21,7 @@ export async function up(next) {
|
||||
`,
|
||||
)
|
||||
await transaction.commit()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
@ -38,6 +39,7 @@ export function down(next) {
|
||||
try {
|
||||
// Rollback your migration here.
|
||||
next()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
next(err)
|
||||
} finally {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
This migration swaps the value stored in Location.lat with the value
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
'This migration adds a fulltext index for the tags in order to search for Hasthags.'
|
||||
@ -9,10 +9,13 @@ export async function up(next) {
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// We do do this in /src/db/migrate/store.ts
|
||||
/*
|
||||
await transaction.run(`
|
||||
CALL db.index.fulltext.createNodeIndex("tag_fulltext_search",["Tag"],["id"])
|
||||
`)
|
||||
await transaction.commit()
|
||||
*/
|
||||
next()
|
||||
} catch (error) {
|
||||
const { message } = error
|
||||
@ -39,10 +42,13 @@ export async function down(next) {
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
// We do do this in /src/db/migrate/store.ts
|
||||
/*
|
||||
await transaction.run(`
|
||||
CALL db.index.fulltext.drop("tag_fulltext_search")
|
||||
`)
|
||||
await transaction.commit()
|
||||
*/
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
We introduced a new node label 'Image' and we need a primary key for it. Best
|
||||
@ -48,6 +48,7 @@ export async function down(next) {
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { existsSync, createReadStream } from 'fs'
|
||||
import path from 'path'
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import https from 'https'
|
||||
import { existsSync, createReadStream } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { S3 } from 'aws-sdk'
|
||||
import mime from 'mime-types'
|
||||
import s3Configs from '../../config'
|
||||
import https from 'https'
|
||||
|
||||
import s3Configs from '@config/index'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
Upload all image files to a S3 compatible object storage in order to reduce
|
||||
@ -95,6 +98,7 @@ export async function down(next) {
|
||||
await transaction.run(``)
|
||||
await transaction.commit()
|
||||
next()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
Refactor all our image properties on posts and users to a dedicated type
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
'We should not maintain obsolete attributes for users who have been deleted.'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
'We should not maintain obsolete attributes for posts which have been deleted.'
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { existsSync } from 'fs'
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import { existsSync } from 'node:fs'
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
In this review:
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
module.exports.up = function (next) {
|
||||
export async function up(next) {
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports.down = function (next) {
|
||||
export async function down(next) {
|
||||
next()
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
This migration adds the clickedCount property to all posts, setting it to 0.
|
||||
`
|
||||
|
||||
module.exports.up = async function (next) {
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
@ -28,7 +28,7 @@ module.exports.up = async function (next) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.down = async function (next) {
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
This migration adds the viewedTeaserCount property to all posts, setting it to 0.
|
||||
`
|
||||
|
||||
module.exports.up = async function (next) {
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
@ -28,7 +28,7 @@ module.exports.up = async function (next) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.down = async function (next) {
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
'This migration adds a Donations node with default settings to the database.'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
We introduced a new node label 'Group' and we need two primary keys 'id' and 'slug' for it.
|
||||
@ -18,9 +18,13 @@ export async function up(next) {
|
||||
// await transaction.run(`
|
||||
// CREATE CONSTRAINT ON ( group:Group ) ASSERT group.slug IS UNIQUE
|
||||
// `)
|
||||
|
||||
// We do do this in /src/db/migrate/store.ts
|
||||
/*
|
||||
await transaction.run(`
|
||||
CALL db.index.fulltext.createNodeIndex("group_fulltext_search",["Group"],["name", "slug", "about", "description"])
|
||||
`)
|
||||
*/
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
@ -42,6 +46,8 @@ export async function down(next) {
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
// We do do this in /src/db/migrate/store.ts
|
||||
/*
|
||||
await transaction.run(`
|
||||
DROP CONSTRAINT ON ( group:Group ) ASSERT group.id IS UNIQUE
|
||||
`)
|
||||
@ -52,6 +58,7 @@ export async function down(next) {
|
||||
CALL db.index.fulltext.drop("group_fulltext_search")
|
||||
`)
|
||||
await transaction.commit()
|
||||
*/
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
|
||||
@ -8,6 +8,8 @@ export async function up(next) {
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// We do do this in /src/db/migrate/store.ts
|
||||
/*
|
||||
// Drop indexes if they exist because due to legacy code they might be set already
|
||||
const indexesResponse = await transaction.run(`CALL db.indexes()`)
|
||||
const indexes = indexesResponse.records.map((record) => record.get('name'))
|
||||
@ -31,6 +33,7 @@ export async function up(next) {
|
||||
`CALL db.index.fulltext.createNodeIndex("tag_fulltext_search",["Tag"],["id"])`,
|
||||
)
|
||||
await transaction.commit()
|
||||
*/
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -50,10 +53,13 @@ export async function down(next) {
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// We do do this in /src/db/migrate/store.ts
|
||||
/*
|
||||
await transaction.run(`CALL db.index.fulltext.drop("user_fulltext_search")`)
|
||||
await transaction.run(`CALL db.index.fulltext.drop("post_fulltext_search")`)
|
||||
await transaction.run(`CALL db.index.fulltext.drop("tag_fulltext_search")`)
|
||||
await transaction.commit()
|
||||
*/
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = 'Add to all existing posts the Article label'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = 'Add postType property Article to all posts'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
Transform event start and end date of format 'YYYY-MM-DD HH:MM:SS' in CEST
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
All authors observe their posts.
|
||||
`
|
||||
|
||||
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 (author:User)-[:WROTE]->(post:Post)
|
||||
MERGE (author)-[obs:OBSERVES]->(post)
|
||||
ON CREATE SET
|
||||
obs.active = true,
|
||||
obs.createdAt = toString(datetime()),
|
||||
obs.updatedAt = toString(datetime())
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(`
|
||||
MATCH (:User)-[obs:OBSERVES]->(p:Post)
|
||||
DELETE obs
|
||||
RETURN p
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description = `
|
||||
All users commenting a post observe the post.
|
||||
`
|
||||
|
||||
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 (commenter:User)-[:WROTE]->(:Comment)-[:COMMENTS]->(post:Post)
|
||||
MERGE (commenter)-[obs:OBSERVES]->(post)
|
||||
ON CREATE SET
|
||||
obs.active = true,
|
||||
obs.createdAt = toString(datetime()),
|
||||
obs.updatedAt = toString(datetime())
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(`
|
||||
MATCH (u:User)-[obs:OBSERVES]->(p:Post)<-[:COMMENTS]-(:Comment)<-[:WROTE]-(u)
|
||||
WHERE NOT (u)-[:WROTE]->(post)
|
||||
DELETE obs
|
||||
RETURN p
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
'Transforms the `sendNotificationEmails` property on User to a multi value system'
|
||||
|
||||
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:User)
|
||||
SET user.emailNotificationsCommentOnObservedPost = user.sendNotificationEmails
|
||||
SET user.emailNotificationsMention = user.sendNotificationEmails
|
||||
SET user.emailNotificationsChatMessage = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberJoined = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberLeft = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberRemoved = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberRoleChanged = user.sendNotificationEmails
|
||||
REMOVE user.sendNotificationEmails
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(`
|
||||
MATCH (user:User)
|
||||
SET user.sendNotificationEmails = user.emailNotificationsMention
|
||||
REMOVE user.emailNotificationsCommentOnObservedPost
|
||||
REMOVE user.emailNotificationsMention
|
||||
REMOVE user.emailNotificationsChatMessage
|
||||
REMOVE user.emailNotificationsGroupMemberJoined
|
||||
REMOVE user.emailNotificationsGroupMemberLeft
|
||||
REMOVE user.emailNotificationsGroupMemberRemoved
|
||||
REMOVE user.emailNotificationsGroupMemberRoleChanged
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
/* eslint-disable import/no-named-as-default-member */
|
||||
import neo4j from 'neo4j-driver'
|
||||
import CONFIG from './../config'
|
||||
import Neode from 'neode'
|
||||
import models from '../models'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import models from '@models/index'
|
||||
|
||||
let driver
|
||||
const defaultOptions = {
|
||||
|
||||
20
backend/src/db/reset-with-migrations.ts
Normal file
20
backend/src/db/reset-with-migrations.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/* eslint-disable n/no-process-exit */
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
import { cleanDatabase } from './factories'
|
||||
|
||||
if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
||||
throw new Error(`You cannot clean the database in a non-staging and real production environment!`)
|
||||
}
|
||||
|
||||
;(async function () {
|
||||
try {
|
||||
await cleanDatabase({ withMigrations: true })
|
||||
console.log('Successfully deleted all nodes and relations including!') // eslint-disable-line no-console
|
||||
process.exit(0)
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) // eslint-disable-line no-console
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
@ -1,5 +1,7 @@
|
||||
import CONFIG from '../config'
|
||||
import { cleanDatabase } from '../db/factories'
|
||||
/* eslint-disable n/no-process-exit */
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
import { cleanDatabase } from './factories'
|
||||
|
||||
if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
||||
throw new Error(`You cannot clean the database in a non-staging and real production environment!`)
|
||||
@ -10,6 +12,7 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
||||
await cleanDatabase()
|
||||
console.log('Successfully deleted all nodes and relations!') // eslint-disable-line no-console
|
||||
process.exit(0)
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) // eslint-disable-line no-console
|
||||
process.exit(1)
|
||||
@ -1,20 +1,23 @@
|
||||
import sample from 'lodash/sample'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import CONFIG from '../config'
|
||||
import createServer from '../server'
|
||||
/* eslint-disable n/no-process-exit */
|
||||
import { faker } from '@faker-js/faker'
|
||||
import Factory from '../db/factories'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import sample from 'lodash/sample'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { categories } from '@constants/categories'
|
||||
import { createCommentMutation } from '@graphql/comments'
|
||||
import {
|
||||
createGroupMutation,
|
||||
joinGroupMutation,
|
||||
changeGroupMemberRoleMutation,
|
||||
} from '../graphql/groups'
|
||||
import { createPostMutation } from '../graphql/posts'
|
||||
import { createRoomMutation } from '../graphql/rooms'
|
||||
import { createMessageMutation } from '../graphql/messages'
|
||||
import { createCommentMutation } from '../graphql/comments'
|
||||
import { categories } from '../constants/categories'
|
||||
} from '@graphql/groups'
|
||||
import { createMessageMutation } from '@graphql/messages'
|
||||
import { createPostMutation } from '@graphql/posts'
|
||||
import { createRoomMutation } from '@graphql/rooms'
|
||||
import createServer from '@src/server'
|
||||
|
||||
import Factory from './factories'
|
||||
import { getNeode, getDriver } from './neo4j'
|
||||
|
||||
if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
||||
throw new Error(`You cannot seed the database in a non-staging and real production environment!`)
|
||||
@ -1565,6 +1568,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
await driver.close()
|
||||
await neode.close()
|
||||
process.exit(0)
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.error(err)
|
||||
|
||||
@ -46,6 +46,8 @@ export const createPostMutation = () => {
|
||||
lng
|
||||
lat
|
||||
}
|
||||
isObservedByMe
|
||||
observingUsersCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
/**
|
||||
* Provide a way to iterate for each element in an array while waiting for async functions to finish
|
||||
*
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable promise/avoid-new */
|
||||
// sometime we have to wait to check a db state by having a look into the db in a certain moment
|
||||
// or we wait a bit to check if we missed to set an await somewhere
|
||||
// see: https://www.sitepoint.com/delay-sleep-pause-wait/
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
/**
|
||||
* iterate through all fields and replace it with the callback result
|
||||
* @property data Array
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import createServer from './server'
|
||||
import CONFIG from './config'
|
||||
import createServer from './server'
|
||||
|
||||
const { server, httpServer } = createServer()
|
||||
const url = new URL(CONFIG.GRAPHQL_URI)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import { getDriver, getNeode } from '../db/neo4j'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getDriver, getNeode } from '@db/neo4j'
|
||||
|
||||
import decode from './decode'
|
||||
import encode from './encode'
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from './../config'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
export default async (driver, authorizationHeader) => {
|
||||
if (!authorizationHeader) return null
|
||||
@ -8,6 +9,7 @@ export default async (driver, authorizationHeader) => {
|
||||
try {
|
||||
const decoded = await jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
id = decoded.sub
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import encode from './encode'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from './../config'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
import encode from './encode'
|
||||
|
||||
describe('encode', () => {
|
||||
let payload
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from './../config'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
// Generate an Access Token for the given User ID
|
||||
export default function encode(user) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import trunc from 'trunc-html'
|
||||
import { DESCRIPTION_EXCERPT_HTML_LENGTH } from '../constants/groups'
|
||||
|
||||
import { DESCRIPTION_EXCERPT_HTML_LENGTH } from '@constants/groups'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import * as cheerio from 'cheerio'
|
||||
import { load } from 'cheerio'
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { exec, build } from 'xregexp/xregexp-all.js'
|
||||
// formats of a Hashtag:
|
||||
// https://en.wikipedia.org/w/index.php?title=Hashtag&oldid=905141980#Style
|
||||
@ -10,7 +11,7 @@ const regX = build('^((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$')
|
||||
|
||||
export default function (content?) {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
const $ = load(content)
|
||||
// We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware.
|
||||
// But we have to know, which Hashtags are removed from the content as well, so we search for the 'a' html-tag.
|
||||
const ids = $('a[data-hashtag-id]')
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { cleanDatabase } from '../../db/factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
let server
|
||||
let query
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import extractHashtags from '../hashtags/extractHashtags'
|
||||
import extractHashtags from './extractHashtags'
|
||||
|
||||
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
||||
if (!hashtags.length) return
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
/* eslint-disable security/detect-unsafe-regex */
|
||||
import linkifyHtml from 'linkify-html'
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
|
||||
export const removeHtmlTags = (input) => {
|
||||
return sanitizeHtml(input, {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import CONFIG from '../../../config'
|
||||
import { cleanHtml } from '../../../middleware/helpers/cleanHtml'
|
||||
import nodemailer from 'nodemailer'
|
||||
import { htmlToText } from 'nodemailer-html-to-text'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { cleanHtml } from '@middleware/helpers/cleanHtml'
|
||||
|
||||
const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT
|
||||
const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD
|
||||
const hasDKIMData =
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import CONFIG from '../../../config'
|
||||
import logosWebapp from '../../../config/logos'
|
||||
import CONFIG from '@config/index'
|
||||
import logosWebapp from '@config/logos'
|
||||
|
||||
import {
|
||||
signupTemplate,
|
||||
emailVerificationTemplate,
|
||||
resetPasswordTemplate,
|
||||
wrongAccountTemplate,
|
||||
notificationTemplate,
|
||||
chatMessageTemplate,
|
||||
} from './templateBuilder'
|
||||
|
||||
const englishHint = 'English version below!'
|
||||
@ -34,6 +36,17 @@ const resetPasswordTemplateData = () => ({
|
||||
name: 'Mr Example',
|
||||
},
|
||||
})
|
||||
const chatMessageTemplateData = {
|
||||
email: 'test@example.org',
|
||||
variables: {
|
||||
senderUser: {
|
||||
name: 'Sender',
|
||||
},
|
||||
recipientUser: {
|
||||
name: 'Recipient',
|
||||
},
|
||||
},
|
||||
}
|
||||
const wrongAccountTemplateData = () => ({
|
||||
email: 'test@example.org',
|
||||
variables: {},
|
||||
@ -163,6 +176,32 @@ describe('templateBuilder', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('chatMessageTemplate', () => {
|
||||
describe('multi language', () => {
|
||||
it('e-mail is build with all data', () => {
|
||||
const subject = `Neue Chat-Nachricht | New chat message - ${chatMessageTemplateData.variables.senderUser.name}`
|
||||
const actionUrl = new URL('/chat', CONFIG.CLIENT_URI).toString()
|
||||
const enContent = `You have received a new chat message from <b>${chatMessageTemplateData.variables.senderUser.name}</b>.`
|
||||
const deContent = `Du hast eine neue Chat-Nachricht von <b>${chatMessageTemplateData.variables.senderUser.name}</b> erhalten.`
|
||||
testEmailData(null, chatMessageTemplate, chatMessageTemplateData, [
|
||||
...textsStandard,
|
||||
{
|
||||
templPropName: 'subject',
|
||||
isContaining: false,
|
||||
text: subject,
|
||||
},
|
||||
englishHint,
|
||||
actionUrl,
|
||||
chatMessageTemplateData.variables.senderUser,
|
||||
chatMessageTemplateData.variables.recipientUser,
|
||||
enContent,
|
||||
deContent,
|
||||
supportUrl,
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('wrongAccountTemplate', () => {
|
||||
describe('multi language', () => {
|
||||
it('e-mail is build with all data', () => {
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
/* eslint-disable import/no-namespace */
|
||||
import mustache from 'mustache'
|
||||
import CONFIG from '../../../config'
|
||||
import metadata from '../../../config/metadata'
|
||||
import logosWebapp from '../../../config/logos'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import logosWebapp from '@config/logos'
|
||||
import metadata from '@config/metadata'
|
||||
|
||||
import * as templates from './templates'
|
||||
import * as templatesEN from './templates/en'
|
||||
import * as templatesDE from './templates/de'
|
||||
import * as templatesEN from './templates/en'
|
||||
|
||||
const from = CONFIG.EMAIL_DEFAULT_SENDER
|
||||
const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI)
|
||||
@ -71,6 +73,26 @@ export const resetPasswordTemplate = ({ email, variables: { nonce, name } }) =>
|
||||
}
|
||||
}
|
||||
|
||||
export const chatMessageTemplate = ({ email, variables: { senderUser, recipientUser } }) => {
|
||||
const subject = `Neue Chat-Nachricht | New chat message - ${senderUser.name}`
|
||||
const actionUrl = new URL('/chat', CONFIG.CLIENT_URI)
|
||||
const renderParams = {
|
||||
...defaultParams,
|
||||
subject,
|
||||
englishHint,
|
||||
actionUrl,
|
||||
senderUser,
|
||||
recipientUser,
|
||||
}
|
||||
|
||||
return {
|
||||
from,
|
||||
to: email,
|
||||
subject,
|
||||
html: mustache.render(templates.layout, renderParams, { content: templates.chatMessage }),
|
||||
}
|
||||
}
|
||||
|
||||
export const wrongAccountTemplate = ({ email, _variables = {} }) => {
|
||||
const subject = 'Falsche Mailadresse? | Wrong E-mail?'
|
||||
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
|
||||
|
||||
105
backend/src/middleware/helpers/email/templates/chatMessage.html
Normal file
105
backend/src/middleware/helpers/email/templates/chatMessage.html
Normal file
@ -0,0 +1,105 @@
|
||||
<!-- Email Body German : BEGIN -->
|
||||
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
|
||||
style="margin: auto;">
|
||||
|
||||
<!-- Hero Image, Flush : BEGIN -->
|
||||
<tr>
|
||||
<td style="background-color: #ffffff;">
|
||||
<img
|
||||
src="{{{ welcomeImageUrl }}}"
|
||||
width="300" height="" alt="Welcome image" border="0"
|
||||
style="width: 100%; max-width: 300px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block; padding: 20px;"
|
||||
class="g-img">
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Hero Image, Flush : END -->
|
||||
|
||||
<!-- 1 Column Text + Button : BEGIN -->
|
||||
<tr>
|
||||
<td style="background-color: #ffffff; padding: 20px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td
|
||||
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
|
||||
<h1
|
||||
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
|
||||
Hallo {{ recipientUser.name }}!</h1>
|
||||
<p style="margin: 0;">Du hast eine neue Chat-Nachricht von <b>{{ senderUser.name }}</b> erhalten.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0 20px;">
|
||||
<!-- Button : BEGIN -->
|
||||
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
|
||||
<tr>
|
||||
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
|
||||
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
|
||||
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Chat anzeigen</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Button : END -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 1 Column Text + Button : END -->
|
||||
|
||||
</table>
|
||||
<!-- Email Body German : END -->
|
||||
|
||||
<!-- Email Body English : BEGIN -->
|
||||
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
|
||||
style="margin: auto;">
|
||||
<tr>
|
||||
<td style="padding: 20px 0; text-align: center">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Hero Image, Flush : BEGIN -->
|
||||
<tr>
|
||||
<td style="background-color: #ffffff;">
|
||||
<img
|
||||
src="{{{ welcomeImageUrl }}}"
|
||||
width="300" height="" alt="Welcome image" border="0"
|
||||
style="width: 100%; max-width: 300px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block; padding: 20px;"
|
||||
class="g-img">
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Hero Image, Flush : END -->
|
||||
|
||||
<!-- 1 Column Text + Button : BEGIN -->
|
||||
<tr>
|
||||
<td style="background-color: #ffffff; padding: 20px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td
|
||||
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
|
||||
<h1
|
||||
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
|
||||
Hello {{ recipientUser.name }}!</h1>
|
||||
<p style="margin: 0;">You have received a new chat message from <b>{{ senderUser.name }}</b>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0 20px;">
|
||||
<!-- Button : BEGIN -->
|
||||
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
|
||||
<tr>
|
||||
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
|
||||
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
|
||||
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Show Chat</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Button : END -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 1 Column Text + Button : END -->
|
||||
|
||||
</table>
|
||||
<!-- Email Body English : END -->
|
||||
@ -1,5 +1,6 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8')
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8')
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8')
|
||||
|
||||
@ -7,5 +8,6 @@ export const signup = readFile('./signup.html')
|
||||
export const passwordReset = readFile('./resetPassword.html')
|
||||
export const wrongAccount = readFile('./wrongAccount.html')
|
||||
export const emailVerification = readFile('./emailVerification.html')
|
||||
export const chatMessage = readFile('./chatMessage.html')
|
||||
|
||||
export const layout = readFile('./layout.html')
|
||||
|
||||
44
backend/src/middleware/helpers/isUserOnline.spec.ts
Normal file
44
backend/src/middleware/helpers/isUserOnline.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { isUserOnline } from './isUserOnline'
|
||||
|
||||
let user
|
||||
|
||||
describe('isUserOnline', () => {
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
lastActiveAt: null,
|
||||
awaySince: null,
|
||||
lastOnlineStatus: null,
|
||||
}
|
||||
})
|
||||
describe('user has lastOnlineStatus `online`', () => {
|
||||
it('returns true if he was active within the last 90 seconds', () => {
|
||||
user.lastOnlineStatus = 'online'
|
||||
user.lastActiveAt = new Date()
|
||||
expect(isUserOnline(user)).toBe(true)
|
||||
})
|
||||
it('returns false if he was not active within the last 90 seconds', () => {
|
||||
user.lastOnlineStatus = 'online'
|
||||
user.lastActiveAt = new Date().getTime() - 90001
|
||||
expect(isUserOnline(user)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user has lastOnlineStatus `away`', () => {
|
||||
it('returns true if he went away less then 180 seconds ago', () => {
|
||||
user.lastOnlineStatus = 'away'
|
||||
user.awaySince = new Date()
|
||||
expect(isUserOnline(user)).toBe(true)
|
||||
})
|
||||
it('returns false if he went away more then 180 seconds ago', () => {
|
||||
user.lastOnlineStatus = 'away'
|
||||
user.awaySince = new Date().getTime() - 180001
|
||||
expect(isUserOnline(user)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is freshly created and has never logged in', () => {
|
||||
it('returns false', () => {
|
||||
expect(isUserOnline(user)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
16
backend/src/middleware/helpers/isUserOnline.ts
Normal file
16
backend/src/middleware/helpers/isUserOnline.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export const isUserOnline = (user) => {
|
||||
// Is Recipient considered online
|
||||
const lastActive = new Date(user.lastActiveAt).getTime()
|
||||
const awaySince = new Date(user.awaySince).getTime()
|
||||
const now = new Date().getTime()
|
||||
const status = user.lastOnlineStatus
|
||||
if (
|
||||
// online & last active less than 1.5min -> online
|
||||
(status === 'online' && now - lastActive < 90000) ||
|
||||
// away for less then 3min -> online
|
||||
(status === 'away' && now - awaySince < 180000)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -1,20 +1,24 @@
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
import { applyMiddleware } from 'graphql-middleware'
|
||||
import CONFIG from './../config'
|
||||
import softDelete from './softDelete/softDeleteMiddleware'
|
||||
import sluggify from './sluggifyMiddleware'
|
||||
import excerpt from './excerptMiddleware'
|
||||
import xss from './xssMiddleware'
|
||||
import permissions from './permissionsMiddleware'
|
||||
import includedFields from './includedFieldsMiddleware'
|
||||
import orderBy from './orderByMiddleware'
|
||||
import validation from './validation/validationMiddleware'
|
||||
import notifications from './notifications/notificationsMiddleware'
|
||||
import hashtags from './hashtags/hashtagsMiddleware'
|
||||
import login from './login/loginMiddleware'
|
||||
import sentry from './sentryMiddleware'
|
||||
import languages from './languages/languages'
|
||||
import userInteractions from './userInteractions'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
import chatMiddleware from './chatMiddleware'
|
||||
import excerpt from './excerptMiddleware'
|
||||
import hashtags from './hashtags/hashtagsMiddleware'
|
||||
import includedFields from './includedFieldsMiddleware'
|
||||
import languages from './languages/languages'
|
||||
import login from './login/loginMiddleware'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import notifications from './notifications/notificationsMiddleware'
|
||||
import orderBy from './orderByMiddleware'
|
||||
import permissions from './permissionsMiddleware'
|
||||
import sentry from './sentryMiddleware'
|
||||
import sluggify from './sluggifyMiddleware'
|
||||
import softDelete from './softDelete/softDeleteMiddleware'
|
||||
import userInteractions from './userInteractions'
|
||||
import validation from './validation/validationMiddleware'
|
||||
import xss from './xssMiddleware'
|
||||
|
||||
export default (schema) => {
|
||||
const middlewares = {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import LanguageDetect from 'languagedetect'
|
||||
import { removeHtmlTags } from '../helpers/cleanHtml'
|
||||
|
||||
import { removeHtmlTags } from '@middleware/helpers/cleanHtml'
|
||||
|
||||
const setPostLanguage = (text, defaultLanguage) => {
|
||||
const lngDetector = new LanguageDetect()
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { sendMail } from '../helpers/email/sendMail'
|
||||
import { sendMail } from '@middleware/helpers/email/sendMail'
|
||||
import {
|
||||
signupTemplate,
|
||||
resetPasswordTemplate,
|
||||
wrongAccountTemplate,
|
||||
emailVerificationTemplate,
|
||||
} from '../helpers/email/templateBuilder'
|
||||
} from '@middleware/helpers/email/templateBuilder'
|
||||
|
||||
const sendSignupMail = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { inviteCode } = args
|
||||
|
||||
430
backend/src/middleware/notifications/followed-users.spec.ts
Normal file
430
backend/src/middleware/notifications/followed-users.spec.ts
Normal file
@ -0,0 +1,430 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import { createGroupMutation } from '@graphql/groups'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
}))
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, firstFollower, secondFollower
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const createPostMutation = gql`
|
||||
mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) {
|
||||
CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const notificationQuery = gql`
|
||||
query ($read: Boolean) {
|
||||
notifications(read: $read, orderBy: updatedAt_desc) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
relatedUser {
|
||||
id
|
||||
}
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Group {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const followUserMutation = gql`
|
||||
mutation ($id: ID!) {
|
||||
followUser(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const createServerResult = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode,
|
||||
driver,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
server = createServerResult.server
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
})
|
||||
|
||||
describe('following users notifications', () => {
|
||||
beforeAll(async () => {
|
||||
postAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'post-author',
|
||||
name: 'Post Author',
|
||||
slug: 'post-author',
|
||||
},
|
||||
{
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
firstFollower = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'first-follower',
|
||||
name: 'First Follower',
|
||||
slug: 'first-follower',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
secondFollower = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'second-follower',
|
||||
name: 'Second Follower',
|
||||
slug: 'second-follower',
|
||||
},
|
||||
{
|
||||
email: 'test3@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
await secondFollower.update({ emailNotificationsFollowingUsers: false })
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await mutate({
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
authenticatedUser = await secondFollower.toJson()
|
||||
await mutate({
|
||||
mutation: followUserMutation,
|
||||
variables: { id: 'post-author' },
|
||||
})
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('the followed user writes a post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: 'This is the content of the post',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the first follower', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the second follower', async () => {
|
||||
authenticatedUser = await secondFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends only one email, as second follower has emails disabled', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('followed user posts in public group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g-1',
|
||||
name: 'A group',
|
||||
description: 'A group to test the follow user notification',
|
||||
groupType: 'public',
|
||||
actionRadius: 'national',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'group-post',
|
||||
title: 'This is the post in the group',
|
||||
content: 'This is the content of the post in the group',
|
||||
groupId: 'g-1',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends a notification to the first follower', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'group-post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('followed user posts in closed group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g-2',
|
||||
name: 'A closed group',
|
||||
description: 'A group to test the follow user notification',
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'closed-group-post',
|
||||
title: 'This is the post in the closed group',
|
||||
content: 'This is the content of the post in the closed group',
|
||||
groupId: 'g-2',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the first follower', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'group-post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('followed user posts in hidden group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g-3',
|
||||
name: 'A hidden group',
|
||||
description: 'A hidden group to test the follow user notification',
|
||||
groupType: 'hidden',
|
||||
actionRadius: 'national',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'hidden-group-post',
|
||||
title: 'This is the post in the hidden group',
|
||||
content: 'This is the content of the post in the hidden group',
|
||||
groupId: 'g-3',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the first follower', async () => {
|
||||
authenticatedUser = await firstFollower.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'group-post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'followed_user_posted',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,8 +1,8 @@
|
||||
import * as cheerio from 'cheerio'
|
||||
import { load } from 'cheerio'
|
||||
|
||||
export default (content?) => {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
const $ = load(content)
|
||||
const userIds = $('a.mention[data-mention-id]')
|
||||
.map((_, el) => {
|
||||
return $(el).attr('data-mention-id')
|
||||
|
||||
@ -1,15 +1,35 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { cleanDatabase } from '../../db/factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer, { pubsub } from '../../server'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import {
|
||||
createGroupMutation,
|
||||
joinGroupMutation,
|
||||
leaveGroupMutation,
|
||||
changeGroupMemberRoleMutation,
|
||||
removeUserFromGroupMutation,
|
||||
} from '../../graphql/groups'
|
||||
} from '@graphql/groups'
|
||||
import { createMessageMutation } from '@graphql/messages'
|
||||
import { createRoomMutation } from '@graphql/rooms'
|
||||
import createServer, { pubsub } from '@src/server'
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
}))
|
||||
|
||||
const chatMessageTemplateMock = jest.fn()
|
||||
const notificationTemplateMock = jest.fn()
|
||||
jest.mock('../helpers/email/templateBuilder', () => ({
|
||||
chatMessageTemplate: () => chatMessageTemplateMock(),
|
||||
notificationTemplate: () => notificationTemplateMock(),
|
||||
}))
|
||||
|
||||
let isUserOnlineMock = jest.fn()
|
||||
jest.mock('../helpers/isUserOnline', () => ({
|
||||
isUserOnline: () => isUserOnlineMock(),
|
||||
}))
|
||||
|
||||
let server, query, mutate, notifiedUser, authenticatedUser
|
||||
let publishSpy
|
||||
@ -68,8 +88,8 @@ afterAll(async () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
publishSpy.mockClear()
|
||||
notifiedUser = await neode.create(
|
||||
'User',
|
||||
notifiedUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
@ -169,6 +189,7 @@ describe('notifications', () => {
|
||||
|
||||
describe('commenter is not me', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
commentContent = 'Commenters comment.'
|
||||
commentAuthor = await neode.create(
|
||||
'User',
|
||||
@ -184,25 +205,8 @@ describe('notifications', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('sends me a notification', async () => {
|
||||
it('sends me a notification and email', async () => {
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
relatedUser: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -210,24 +214,85 @@ describe('notifications', () => {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
).resolves.toMatchObject(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
relatedUser: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('sends me no notification if I have blocked the comment author', async () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: { notifications: [] },
|
||||
})
|
||||
describe('if I have disabled `emailNotificationsCommentOnObservedPost`', () => {
|
||||
it('sends me a notification but no email', async () => {
|
||||
await notifiedUser.update({ emailNotificationsCommentOnObservedPost: false })
|
||||
await createCommentOnPostAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
relatedUser: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
// No Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('if I have blocked the comment author', () => {
|
||||
it('sends me no notification', async () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: { notifications: [] },
|
||||
})
|
||||
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -238,7 +303,6 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
it('sends me no notification', async () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: { notifications: [] },
|
||||
@ -257,6 +321,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
{
|
||||
@ -279,7 +344,7 @@ describe('notifications', () => {
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?'
|
||||
})
|
||||
|
||||
it('sends me a notification', async () => {
|
||||
it('sends me a notification and email', async () => {
|
||||
await createPostAction()
|
||||
const expectedContent =
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||
@ -307,6 +372,47 @@ describe('notifications', () => {
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if I have disabled `emailNotificationsMention`', () => {
|
||||
it('sends me a notification but no email', async () => {
|
||||
await notifiedUser.update({ emailNotificationsMention: false })
|
||||
await createPostAction()
|
||||
const expectedContent =
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'mentioned_in_post',
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'p47',
|
||||
content: expectedContent,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('publishes `NOTIFICATION_ADDED` to me', async () => {
|
||||
@ -634,12 +740,121 @@ describe('notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('chat email notifications', () => {
|
||||
let chatSender
|
||||
let chatReceiver
|
||||
let roomId
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
chatSender = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'chatSender',
|
||||
name: 'chatSender',
|
||||
slug: 'chatSender',
|
||||
},
|
||||
{
|
||||
email: 'chatSender@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
|
||||
chatReceiver = await Factory.build(
|
||||
'user',
|
||||
{ id: 'chatReceiver', name: 'chatReceiver', slug: 'chatReceiver' },
|
||||
{ email: 'user@example.org' },
|
||||
)
|
||||
|
||||
authenticatedUser = await chatSender.toJson()
|
||||
|
||||
const room = await mutate({
|
||||
mutation: createRoomMutation(),
|
||||
variables: {
|
||||
userId: 'chatReceiver',
|
||||
},
|
||||
})
|
||||
roomId = room.data.CreateRoom.id
|
||||
})
|
||||
|
||||
describe('if the chatReceiver is online', () => {
|
||||
it('sends no email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(true)
|
||||
|
||||
await mutate({
|
||||
mutation: createMessageMutation(),
|
||||
variables: {
|
||||
roomId,
|
||||
content: 'Some nice message to chatReceiver',
|
||||
},
|
||||
})
|
||||
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('if the chatReceiver is offline', () => {
|
||||
it('sends an email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
|
||||
await mutate({
|
||||
mutation: createMessageMutation(),
|
||||
variables: {
|
||||
roomId,
|
||||
content: 'Some nice message to chatReceiver',
|
||||
},
|
||||
})
|
||||
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(chatMessageTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('if the chatReceiver has blocked chatSender', () => {
|
||||
it('sends no email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
await chatReceiver.relateTo(chatSender, 'blocked')
|
||||
|
||||
await mutate({
|
||||
mutation: createMessageMutation(),
|
||||
variables: {
|
||||
roomId,
|
||||
content: 'Some nice message to chatReceiver',
|
||||
},
|
||||
})
|
||||
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('if the chatReceiver has disabled `emailNotificationsChatMessage`', () => {
|
||||
it('sends no email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
await chatReceiver.update({ emailNotificationsChatMessage: false })
|
||||
|
||||
await mutate({
|
||||
mutation: createMessageMutation(),
|
||||
variables: {
|
||||
roomId,
|
||||
content: 'Some nice message to chatReceiver',
|
||||
},
|
||||
})
|
||||
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('group notifications', () => {
|
||||
let groupOwner
|
||||
|
||||
beforeEach(async () => {
|
||||
groupOwner = await neode.create(
|
||||
'User',
|
||||
groupOwner = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'group-owner',
|
||||
name: 'Group Owner',
|
||||
@ -666,7 +881,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
describe('user joins group', () => {
|
||||
beforeEach(async () => {
|
||||
const joinGroupAction = async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
@ -676,9 +891,14 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has the notification in database', async () => {
|
||||
it('sends the group owner a notification and email', async () => {
|
||||
await joinGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -702,19 +922,50 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if the group owner has disabled `emailNotificationsGroupMemberJoined`', () => {
|
||||
it('sends the group owner a notification but no email', async () => {
|
||||
await groupOwner.update({ emailNotificationsGroupMemberJoined: false })
|
||||
await joinGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'user_joined_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'you',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user leaves group', () => {
|
||||
beforeEach(async () => {
|
||||
describe('user joins and leaves group', () => {
|
||||
const leaveGroupAction = async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: leaveGroupMutation(),
|
||||
variables: {
|
||||
@ -723,9 +974,22 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('has two the notification in database', async () => {
|
||||
it('sends the group owner two notifications and emails', async () => {
|
||||
await leaveGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -761,19 +1025,61 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
describe('if the group owner has disabled `emailNotificationsGroupMemberLeft`', () => {
|
||||
it('sends the group owner two notification but only only one email', async () => {
|
||||
await groupOwner.update({ emailNotificationsGroupMemberLeft: false })
|
||||
await leaveGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'user_left_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'you',
|
||||
},
|
||||
},
|
||||
{
|
||||
read: false,
|
||||
reason: 'user_joined_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'you',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user role in group changes', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
const changeGroupMemberRoleAction = async () => {
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
@ -784,9 +1090,23 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
// Clear after because the above generates a notification not related
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has notification in database', async () => {
|
||||
it('sends the group member a notification and email', async () => {
|
||||
await changeGroupMemberRoleAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -810,19 +1130,49 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if the group member has disabled `emailNotificationsGroupMemberRoleChanged`', () => {
|
||||
it('sends the group member a notification but no email', async () => {
|
||||
notifiedUser.update({ emailNotificationsGroupMemberRoleChanged: false })
|
||||
await changeGroupMemberRoleAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'changed_group_member_role',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'group-owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is removed from group', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
const removeUserFromGroupAction = async () => {
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
await mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
@ -832,9 +1182,23 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
// Clear after because the above generates a notification not related
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has notification in database', async () => {
|
||||
it('sends the previous group member a notification and email', async () => {
|
||||
await removeUserFromGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -858,6 +1222,44 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if the previous group member has disabled `emailNotificationsGroupMemberRemoved`', () => {
|
||||
it('sends the previous group member a notification but no email', async () => {
|
||||
notifiedUser.update({ emailNotificationsGroupMemberRemoved: false })
|
||||
await removeUserFromGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'removed_user_from_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'group-owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
import { pubsub, NOTIFICATION_ADDED } from '../../server'
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
import { sendMail } from '@middleware/helpers/email/sendMail'
|
||||
import {
|
||||
chatMessageTemplate,
|
||||
notificationTemplate,
|
||||
} from '@middleware/helpers/email/templateBuilder'
|
||||
import { isUserOnline } from '@middleware/helpers/isUserOnline'
|
||||
import { validateNotifyUsers } from '@middleware/validation/validationMiddleware'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { pubsub, NOTIFICATION_ADDED } from '@src/server'
|
||||
|
||||
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
||||
import { validateNotifyUsers } from '../validation/validationMiddleware'
|
||||
import { sendMail } from '../helpers/email/sendMail'
|
||||
import { notificationTemplate } from '../helpers/email/templateBuilder'
|
||||
|
||||
const queryNotificationEmails = async (context, notificationUserIds) => {
|
||||
if (!(notificationUserIds && notificationUserIds.length)) return []
|
||||
@ -31,7 +38,7 @@ const queryNotificationEmails = async (context, notificationUserIds) => {
|
||||
}
|
||||
}
|
||||
|
||||
const publishNotifications = async (context, promises) => {
|
||||
const publishNotifications = async (context, promises, emailNotificationSetting: string) => {
|
||||
let notifications = await Promise.all(promises)
|
||||
notifications = notifications.flat()
|
||||
const notificationsEmailAddresses = await queryNotificationEmails(
|
||||
@ -40,7 +47,7 @@ const publishNotifications = async (context, promises) => {
|
||||
)
|
||||
notifications.forEach((notificationAdded, index) => {
|
||||
pubsub.publish(NOTIFICATION_ADDED, { notificationAdded })
|
||||
if (notificationAdded.to.sendNotificationEmails) {
|
||||
if (notificationAdded.to[emailNotificationSetting] ?? true) {
|
||||
sendMail(
|
||||
notificationTemplate({
|
||||
email: notificationsEmailAddresses[index].email,
|
||||
@ -55,9 +62,11 @@ const handleJoinGroup = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context)],
|
||||
'emailNotificationsGroupMemberJoined',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
@ -66,9 +75,11 @@ const handleLeaveGroup = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyOwnersOfGroup(groupId, userId, 'user_left_group', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyOwnersOfGroup(groupId, userId, 'user_left_group', context)],
|
||||
'emailNotificationsGroupMemberLeft',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
@ -77,9 +88,11 @@ const handleChangeGroupMemberRole = async (resolve, root, args, context, resolve
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context)],
|
||||
'emailNotificationsGroupMemberRoleChanged',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
@ -88,34 +101,65 @@ const handleRemoveUserFromGroup = async (resolve, root, args, context, resolveIn
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context)],
|
||||
'emailNotificationsGroupMemberRemoved',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { groupId } = args
|
||||
const idsOfUsers = extractMentionedUsers(args.content)
|
||||
const post = await resolve(root, args, context, resolveInfo)
|
||||
if (post) {
|
||||
await publishNotifications(context, [
|
||||
notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)],
|
||||
'emailNotificationsMention',
|
||||
)
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyFollowingUsers(post.id, groupId, context)],
|
||||
'emailNotificationsFollowingUsers',
|
||||
)
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyGroupMembersOfNewPost(post.id, groupId, context)],
|
||||
'emailNotificationsPostInGroup',
|
||||
)
|
||||
}
|
||||
return post
|
||||
}
|
||||
|
||||
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { content } = args
|
||||
let idsOfUsers = extractMentionedUsers(content)
|
||||
let idsOfMentionedUsers = extractMentionedUsers(content)
|
||||
const comment = await resolve(root, args, context, resolveInfo)
|
||||
const [postAuthor] = await postAuthorOfComment(comment.id, { context })
|
||||
idsOfUsers = idsOfUsers.filter((id) => id !== postAuthor.id)
|
||||
await publishNotifications(context, [
|
||||
notifyUsersOfMention('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context),
|
||||
notifyUsersOfComment('Comment', comment.id, postAuthor.id, 'commented_on_post', context),
|
||||
])
|
||||
idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id)
|
||||
await publishNotifications(
|
||||
context,
|
||||
[
|
||||
notifyUsersOfMention(
|
||||
'Comment',
|
||||
comment.id,
|
||||
idsOfMentionedUsers,
|
||||
'mentioned_in_comment',
|
||||
context,
|
||||
),
|
||||
],
|
||||
'emailNotificationsMention',
|
||||
)
|
||||
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context)],
|
||||
'emailNotificationsCommentOnObservedPost',
|
||||
)
|
||||
|
||||
return comment
|
||||
}
|
||||
|
||||
@ -138,6 +182,88 @@ const postAuthorOfComment = async (commentId, { context }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const notifyFollowingUsers = async (postId, groupId, context) => {
|
||||
const reason = 'followed_user_posted'
|
||||
const cypher = `
|
||||
MATCH (post:Post { id: $postId })<-[:WROTE]-(author:User { id: $userId })<-[:FOLLOWS]-(user:User)
|
||||
OPTIONAL MATCH (post)-[:IN]->(group:Group { id: $groupId })
|
||||
WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public'
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, author, user,
|
||||
post {.*, author: properties(author) } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(user),
|
||||
relatedUser: properties(author)
|
||||
}
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
postId,
|
||||
reason,
|
||||
groupId: groupId || null,
|
||||
userId: context.user.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
|
||||
if (!groupId) return []
|
||||
const reason = 'post_in_group'
|
||||
const cypher = `
|
||||
MATCH (post:Post { id: $postId })<-[:WROTE]-(author:User { id: $userId })
|
||||
MATCH (post)-[:IN]->(group:Group { id: $groupId })<-[membership:MEMBER_OF]-(user:User)
|
||||
WHERE NOT membership.role = 'pending'
|
||||
AND NOT (user)-[:MUTED]->(group)
|
||||
AND NOT user.id = $userId
|
||||
WITH post, author, user
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, author, user,
|
||||
post {.*, author: properties(author) } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(user),
|
||||
relatedUser: properties(author)
|
||||
}
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
postId,
|
||||
reason,
|
||||
groupId,
|
||||
userId: context.user.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
const notifyOwnersOfGroup = async (groupId, userId, reason, context) => {
|
||||
const cypher = `
|
||||
MATCH (user:User { id: $userId })
|
||||
@ -269,29 +395,34 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
}
|
||||
}
|
||||
|
||||
const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
|
||||
if (context.user.id === postAuthorId) return []
|
||||
const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
||||
await validateNotifyUsers(label, reason)
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||
WHERE NOT (postAuthor)-[:BLOCKED]-(commenter)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor)
|
||||
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||
WHERE NOT (observingUser)-[:BLOCKED]-(commenter) AND NOT observingUser.id = $userId
|
||||
WITH observingUser, post, comment, commenter
|
||||
MATCH (postAuthor:User)-[:WROTE]->(post)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, postAuthor, post, commenter,
|
||||
WITH notification, observingUser, post, commenter, postAuthor,
|
||||
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(postAuthor),
|
||||
to: properties(observingUser),
|
||||
relatedUser: properties(commenter)
|
||||
}
|
||||
`,
|
||||
{ commentId, postAuthorId, reason },
|
||||
{
|
||||
commentId,
|
||||
reason,
|
||||
userId: context.user.id,
|
||||
},
|
||||
)
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
@ -303,6 +434,57 @@ const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, cont
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateMessage = async (resolve, root, args, context, resolveInfo) => {
|
||||
// Execute resolver
|
||||
const result = await resolve(root, args, context, resolveInfo)
|
||||
|
||||
// Query Parameters
|
||||
const { roomId } = args
|
||||
const {
|
||||
user: { id: currentUserId },
|
||||
} = context
|
||||
|
||||
// Find Recipient
|
||||
const session = context.driver.session()
|
||||
const messageRecipient = session.readTransaction(async (transaction) => {
|
||||
const messageRecipientCypher = `
|
||||
MATCH (senderUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
||||
MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WHERE NOT recipientUser.id = $currentUserId
|
||||
AND NOT (recipientUser)-[:BLOCKED]-(senderUser)
|
||||
AND NOT recipientUser.emailNotificationsChatMessage = false
|
||||
RETURN senderUser {.*}, recipientUser {.*}, emailAddress {.email}
|
||||
`
|
||||
const txResponse = await transaction.run(messageRecipientCypher, {
|
||||
currentUserId,
|
||||
roomId,
|
||||
})
|
||||
|
||||
return {
|
||||
senderUser: await txResponse.records.map((record) => record.get('senderUser'))[0],
|
||||
recipientUser: await txResponse.records.map((record) => record.get('recipientUser'))[0],
|
||||
email: await txResponse.records.map((record) => record.get('emailAddress'))[0]?.email,
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
// Execute Query
|
||||
const { senderUser, recipientUser, email } = await messageRecipient
|
||||
|
||||
// Send EMail if we found a user(not blocked) and he is not considered online
|
||||
if (recipientUser && !isUserOnline(recipientUser)) {
|
||||
void sendMail(chatMessageTemplate({ email, variables: { senderUser, recipientUser } }))
|
||||
}
|
||||
|
||||
// Return resolver result to client
|
||||
return result
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreatePost: handleContentDataOfPost,
|
||||
@ -313,5 +495,6 @@ export default {
|
||||
LeaveGroup: handleLeaveGroup,
|
||||
ChangeGroupMemberRole: handleChangeGroupMemberRole,
|
||||
RemoveUserFromGroup: handleRemoveUserFromGroup,
|
||||
CreateMessage: handleCreateMessage,
|
||||
},
|
||||
}
|
||||
|
||||
377
backend/src/middleware/notifications/observing-posts.spec.ts
Normal file
377
backend/src/middleware/notifications/observing-posts.spec.ts
Normal file
@ -0,0 +1,377 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, firstCommenter, secondCommenter
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const createPostMutation = gql`
|
||||
mutation ($id: ID, $title: String!, $content: String!) {
|
||||
CreatePost(id: $id, title: $title, content: $content) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const createCommentMutation = gql`
|
||||
mutation ($id: ID, $postId: ID!, $content: String!) {
|
||||
CreateComment(id: $id, postId: $postId, content: $content) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const notificationQuery = gql`
|
||||
query ($read: Boolean) {
|
||||
notifications(read: $read, orderBy: updatedAt_desc) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
relatedUser {
|
||||
id
|
||||
}
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Group {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const toggleObservePostMutation = gql`
|
||||
mutation ($id: ID!, $value: Boolean!) {
|
||||
toggleObservePost(id: $id, value: $value) {
|
||||
isObservedByMe
|
||||
observingUsersCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const createServerResult = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode,
|
||||
driver,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
server = createServerResult.server
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
})
|
||||
|
||||
describe('notifications for users that observe a post', () => {
|
||||
beforeAll(async () => {
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'post-author',
|
||||
name: 'Post Author',
|
||||
slug: 'post-author',
|
||||
},
|
||||
{
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
firstCommenter = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'first-commenter',
|
||||
name: 'First Commenter',
|
||||
slug: 'first-commenter',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
secondCommenter = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'second-commenter',
|
||||
name: 'Second Commenter',
|
||||
slug: 'second-commenter',
|
||||
},
|
||||
{
|
||||
email: 'test3@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: 'This is the content of the post',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('first comment on the post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post',
|
||||
id: 'c-1',
|
||||
content: 'first comment of first commenter',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the commenter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the author', async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-1',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
describe('second comment on post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await secondCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post',
|
||||
id: 'c-2',
|
||||
content: 'first comment of second commenter',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the commenter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the author', async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-1',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to first commenter', async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('first commenter unfollows the post and post author comments post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: toggleObservePostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
value: false,
|
||||
},
|
||||
})
|
||||
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post',
|
||||
id: 'c-3',
|
||||
content: 'first comment of post author',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends no new notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-1',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends no new notification to first commenter', async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to second commenter', async () => {
|
||||
authenticatedUser = await secondCommenter.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-3',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
400
backend/src/middleware/notifications/posts-in-groups.spec.ts
Normal file
400
backend/src/middleware/notifications/posts-in-groups.spec.ts
Normal file
@ -0,0 +1,400 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import {
|
||||
createGroupMutation,
|
||||
joinGroupMutation,
|
||||
changeGroupMemberRoleMutation,
|
||||
} from '@graphql/groups'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const sendMailMock = jest.fn()
|
||||
jest.mock('../helpers/email/sendMail', () => ({
|
||||
sendMail: () => sendMailMock(),
|
||||
}))
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, groupMember, pendingMember
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const createPostMutation = gql`
|
||||
mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) {
|
||||
CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const notificationQuery = gql`
|
||||
query ($read: Boolean) {
|
||||
notifications(read: $read, orderBy: updatedAt_desc) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
relatedUser {
|
||||
id
|
||||
}
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Group {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const muteGroupMutation = gql`
|
||||
mutation ($groupId: ID!) {
|
||||
muteGroup(groupId: $groupId) {
|
||||
id
|
||||
isMutedByMe
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const unmuteGroupMutation = gql`
|
||||
mutation ($groupId: ID!) {
|
||||
unmuteGroup(groupId: $groupId) {
|
||||
id
|
||||
isMutedByMe
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const markAllAsRead = async () =>
|
||||
mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
markAllAsRead {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const createServerResult = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode,
|
||||
driver,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
server = createServerResult.server
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
})
|
||||
|
||||
describe('notify group members of new posts in group', () => {
|
||||
beforeAll(async () => {
|
||||
postAuthor = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'post-author',
|
||||
name: 'Post Author',
|
||||
slug: 'post-author',
|
||||
},
|
||||
{
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
groupMember = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'group-member',
|
||||
name: 'Group Member',
|
||||
slug: 'group-member',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
pendingMember = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'pending-member',
|
||||
name: 'Pending Member',
|
||||
slug: 'pending-member',
|
||||
},
|
||||
{
|
||||
email: 'test3@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g-1',
|
||||
name: 'A closed group',
|
||||
description: 'A closed group to test the notifications to group members',
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g-1',
|
||||
userId: 'group-member',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await pendingMember.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g-1',
|
||||
userId: 'pending-member',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g-1',
|
||||
userId: 'group-member',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('group owner posts in group', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await markAllAsRead()
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await markAllAsRead()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the new post in the group',
|
||||
content: 'This is the content of the new post in the group',
|
||||
groupId: 'g-1',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the author of the post', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the pending group member', async () => {
|
||||
authenticatedUser = await pendingMember.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the group member', async () => {
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends one email', () => {
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('group member mutes group', () => {
|
||||
it('sets the muted status correctly', async () => {
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: muteGroupMutation,
|
||||
variables: {
|
||||
groupId: 'g-1',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
muteGroup: {
|
||||
isMutedByMe: true,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification when another post is posted', async () => {
|
||||
jest.clearAllMocks()
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await markAllAsRead()
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'post-1',
|
||||
title: 'This is another post in the group',
|
||||
content: 'This is the content of another post in the group',
|
||||
groupId: 'g-1',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('group member unmutes group again but disables email', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
await groupMember.update({ emailNotificationsPostInGroup: false })
|
||||
})
|
||||
|
||||
it('sets the muted status correctly', async () => {
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: unmuteGroupMutation,
|
||||
variables: {
|
||||
groupId: 'g-1',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
unmuteGroup: {
|
||||
isMutedByMe: false,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification when another post is posted', async () => {
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await markAllAsRead()
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'post-2',
|
||||
title: 'This is yet another post in the group',
|
||||
content: 'This is the content of yet another post in the group',
|
||||
groupId: 'g-1',
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupMember.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'post_in_group',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO email', () => {
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,8 +1,9 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { cleanDatabase } from '../db/factories'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import gql from 'graphql-tag'
|
||||
import { getDriver, getNeode } from '../db/neo4j'
|
||||
import CONFIG from '../config'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getDriver, getNeode } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
const instance = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { rule, shield, deny, allow, or, and } from 'graphql-shield'
|
||||
import { getNeode } from '../db/neo4j'
|
||||
import CONFIG from '../config'
|
||||
import { validateInviteCode } from '../schema/resolvers/transactions/inviteCodes'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { getNeode } from '@db/neo4j'
|
||||
import { validateInviteCode } from '@schema/resolvers/transactions/inviteCodes'
|
||||
|
||||
const debug = !!CONFIG.DEBUG
|
||||
const allowExternalErrors = true
|
||||
@ -383,7 +384,7 @@ export default shield(
|
||||
Tag: allow,
|
||||
reports: isModerator,
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
currentUser: isAuthenticated,
|
||||
Group: isAuthenticated,
|
||||
GroupMembers: isAllowedSeeingGroupMembers,
|
||||
GroupCount: isAuthenticated,
|
||||
@ -391,7 +392,6 @@ export default shield(
|
||||
profilePagePosts: allow,
|
||||
Comment: allow,
|
||||
User: or(noEmailFilter, isAdmin),
|
||||
isLoggedIn: allow,
|
||||
Badge: allow,
|
||||
PostsEmotionsCountByEmotion: allow,
|
||||
PostsEmotionsByCurrentUser: isAuthenticated,
|
||||
@ -462,12 +462,17 @@ export default shield(
|
||||
switchUserRole: isAdmin,
|
||||
markTeaserAsViewed: allow,
|
||||
saveCategorySettings: isAuthenticated,
|
||||
updateOnlineStatus: isAuthenticated,
|
||||
CreateRoom: isAuthenticated,
|
||||
CreateMessage: isAuthenticated,
|
||||
MarkMessagesAsSeen: isAuthenticated,
|
||||
toggleObservePost: isAuthenticated,
|
||||
muteGroup: and(isAuthenticated, isMemberOfGroup),
|
||||
unmuteGroup: and(isAuthenticated, isMemberOfGroup),
|
||||
},
|
||||
User: {
|
||||
email: or(isMyOwn, isAdmin),
|
||||
emailNotificationSettings: isMyOwn,
|
||||
},
|
||||
Report: isModerator,
|
||||
},
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { sentry } from 'graphql-middleware-sentry'
|
||||
import CONFIG from '../config'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
let sentryMiddleware: any = (resolve, root, args, context, resolveInfo) =>
|
||||
resolve(root, args, context, resolveInfo)
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import createServer from '../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import { createGroupMutation, updateGroupMutation } from '../graphql/groups'
|
||||
import { createPostMutation } from '../graphql/posts'
|
||||
import { signupVerificationMutation } from '../graphql/authentications'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import { signupVerificationMutation } from '@graphql/authentications'
|
||||
import { createGroupMutation, updateGroupMutation } from '@graphql/groups'
|
||||
import { createPostMutation } from '@graphql/posts'
|
||||
import createServer from '@src/server'
|
||||
|
||||
let authenticatedUser
|
||||
let variables
|
||||
@ -21,6 +22,9 @@ const { server } = createServer({
|
||||
driver,
|
||||
neode,
|
||||
user: authenticatedUser,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import createServer from '../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
let query, aUser, bUser, post, authenticatedUser, variables
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import gql from 'graphql-tag'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
@ -101,7 +101,13 @@ const validateReview = async (resolve, root, args, context, info) => {
|
||||
}
|
||||
|
||||
export const validateNotifyUsers = async (label, reason) => {
|
||||
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
|
||||
const reasonsAllowed = [
|
||||
'mentioned_in_post',
|
||||
'mentioned_in_comment',
|
||||
'commented_on_post',
|
||||
'followed_user_posted',
|
||||
'post_in_group',
|
||||
]
|
||||
if (!reasonsAllowed.includes(reason)) throw new Error('Notification reason is not allowed!')
|
||||
if (
|
||||
(label === 'Post' && reason !== 'mentioned_in_post') ||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user