mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into 5982-fix-donation-bar-blink
This commit is contained in:
commit
7b1f4cb7e5
209
.github/dependabot.yml
vendored
209
.github/dependabot.yml
vendored
@ -1,179 +1,68 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: cypress
|
||||
versions:
|
||||
- 6.3.0
|
||||
- 6.4.0
|
||||
- 6.5.0
|
||||
- 6.6.0
|
||||
- 6.7.1
|
||||
- 6.8.0
|
||||
- 7.0.0
|
||||
- 7.0.1
|
||||
- 7.1.0
|
||||
- dependency-name: cypress-cucumber-preprocessor
|
||||
versions:
|
||||
- 4.0.0
|
||||
- 4.0.1
|
||||
- 4.0.3
|
||||
- dependency-name: date-fns
|
||||
versions:
|
||||
- 2.16.1
|
||||
- 2.17.0
|
||||
- 2.18.0
|
||||
- 2.19.0
|
||||
- 2.20.0
|
||||
- 2.20.1
|
||||
- 2.20.2
|
||||
- 2.20.3
|
||||
- 2.21.0
|
||||
- dependency-name: cypress-file-upload
|
||||
versions:
|
||||
- 5.0.2
|
||||
- 5.0.3
|
||||
- 5.0.4
|
||||
- 5.0.5
|
||||
- dependency-name: neo4j-driver
|
||||
versions:
|
||||
- 4.2.2
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
- package-ecosystem: npm
|
||||
directory: "/backend"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: y18n
|
||||
versions:
|
||||
- 4.0.1
|
||||
- 4.0.2
|
||||
- dependency-name: metascraper-publisher
|
||||
versions:
|
||||
- 5.16.16
|
||||
- 5.18.1
|
||||
- 5.18.12
|
||||
- 5.18.2
|
||||
- 5.18.4
|
||||
- 5.18.5
|
||||
- 5.18.6
|
||||
- 5.18.9
|
||||
- 5.20.0
|
||||
- 5.21.0
|
||||
- 5.21.2
|
||||
- 5.21.3
|
||||
- 5.21.4
|
||||
- 5.21.5
|
||||
- dependency-name: metascraper-author
|
||||
versions:
|
||||
- 5.16.16
|
||||
- 5.18.1
|
||||
- 5.18.12
|
||||
- 5.18.2
|
||||
- 5.18.4
|
||||
- 5.18.5
|
||||
- 5.18.6
|
||||
- 5.18.9
|
||||
- 5.20.0
|
||||
- 5.21.0
|
||||
- 5.21.2
|
||||
- 5.21.3
|
||||
- 5.21.4
|
||||
- 5.21.5
|
||||
- dependency-name: neo4j-driver
|
||||
versions:
|
||||
- 4.2.2
|
||||
- dependency-name: neo4j-graphql-js
|
||||
versions:
|
||||
- 2.19.1
|
||||
- dependency-name: mustache
|
||||
versions:
|
||||
- 4.1.0
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
- package-ecosystem: npm
|
||||
directory: "/webapp"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: nuxt
|
||||
versions:
|
||||
- 2.14.12
|
||||
- 2.15.0
|
||||
- 2.15.1
|
||||
- 2.15.2
|
||||
- 2.15.3
|
||||
- dependency-name: v-tooltip
|
||||
versions:
|
||||
- 2.1.2
|
||||
- dependency-name: "@vue/server-test-utils"
|
||||
versions:
|
||||
- 1.1.2
|
||||
- 1.1.3
|
||||
- dependency-name: node-notifier
|
||||
versions:
|
||||
- 8.0.1
|
||||
- package-ecosystem: docker
|
||||
directory: "/webapp"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: node
|
||||
versions:
|
||||
- ">= 15.5.a, < 15.6"
|
||||
- dependency-name: node
|
||||
versions:
|
||||
- 15.10.0.pre.alpine3.10
|
||||
- 15.11.0.pre.alpine3.10
|
||||
- 15.12.0.pre.alpine3.10
|
||||
- 15.13.0.pre.alpine3.10
|
||||
- 15.7.0.pre.alpine3.10
|
||||
- 15.8.0.pre.alpine3.10
|
||||
- 15.9.0.pre.alpine3.10
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: "/backend"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: node
|
||||
versions:
|
||||
- ">= 15.4.a, < 15.5"
|
||||
- dependency-name: node
|
||||
versions:
|
||||
- ">= 15.5.a, < 15.6"
|
||||
- dependency-name: node
|
||||
versions:
|
||||
- 15.10.0.pre.alpine3.10
|
||||
- 15.11.0.pre.alpine3.10
|
||||
- 15.12.0.pre.alpine3.10
|
||||
- 15.13.0.pre.alpine3.10
|
||||
- 15.7.0.pre.alpine3.10
|
||||
- 15.8.0.pre.alpine3.10
|
||||
- 15.9.0.pre.alpine3.10
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
- package-ecosystem: docker
|
||||
directory: "/webapp"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
- package-ecosystem: docker
|
||||
directory: "/neo4j"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: neo4j
|
||||
versions:
|
||||
- 4.2.3
|
||||
- 4.2.4
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
- package-ecosystem: docker
|
||||
directory: "/deployment/legacy-migration/maintenance-worker"
|
||||
directory: "/deployment/src/docker"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
|
||||
2
.github/workflows/lint_pr.yml
vendored
2
.github/workflows/lint_pr.yml
vendored
@ -1,7 +1,6 @@
|
||||
name: "ocelot.social lint pull request CI"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
@ -12,6 +11,7 @@ jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
|
||||
26
.github/workflows/publish-branded.yml
vendored
26
.github/workflows/publish-branded.yml
vendored
@ -1,9 +1,7 @@
|
||||
name: ocelot.social publish branded CI
|
||||
|
||||
name: publish-branded
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
repository_dispatch:
|
||||
types: [trigger-build-success]
|
||||
jobs:
|
||||
build_branded:
|
||||
name: Docker Build Branded
|
||||
@ -11,7 +9,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
with:
|
||||
ref: ${{ github.event.client_payload.ref }}
|
||||
- name: Setup env
|
||||
run: echo "OCELOT_VERSION=${{ github.event.client_payload.BUILD_VERSION }}" >> $GITHUB_ENV
|
||||
- name: Build branded images
|
||||
run: |
|
||||
deployment/scripts/branded-images.build.sh
|
||||
@ -47,6 +48,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.client_payload.ref }}
|
||||
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v2
|
||||
@ -73,13 +76,4 @@ jobs:
|
||||
run: docker load < /tmp/maintenance-branded.tar
|
||||
|
||||
- name: Upload to dockerhub
|
||||
run: deployment/scripts/branded-images.upload.sh
|
||||
# - name: login to dockerhub
|
||||
# run: echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
|
||||
#
|
||||
# - name: Push Backend
|
||||
# run: docker push --all-tags ocelotsocialnetwork/backend-branded
|
||||
# - name: Push Webapp
|
||||
# run: docker push --all-tags ocelotsocialnetwork/webapp-branded
|
||||
# - name: Push Maintenance
|
||||
# run: docker push --all-tags ocelotsocialnetwork/maintenance-branded
|
||||
run: deployment/scripts/branded-images.upload.sh
|
||||
471
.github/workflows/publish.yml
vendored
471
.github/workflows/publish.yml
vendored
@ -1,64 +1,36 @@
|
||||
name: ocelot.social publish CI
|
||||
name: publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
# - 5059-epic-groups # for testing while developing
|
||||
# template branches in repo
|
||||
# - template--separate-branch-auto-deployment--5059-epic-groups
|
||||
|
||||
jobs:
|
||||
##############################################################################
|
||||
# JOB: PREPARE ###############################################################
|
||||
##############################################################################
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
# needs: [nothing]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# TODO: DO STUFF ??? #####################################################
|
||||
##########################################################################
|
||||
- name: Check translation files
|
||||
run: |
|
||||
scripts/translations/sort.sh
|
||||
scripts/translations/missing-keys.sh
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD COMMUNITY NEO4J ##########################################
|
||||
##############################################################################
|
||||
build_production_neo4j:
|
||||
name: Docker Build Production - Neo4J
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# SET ENVS ###############################################################
|
||||
##########################################################################
|
||||
- name: ENV - VERSION
|
||||
run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_DATE
|
||||
run: echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_VERSION
|
||||
run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_COMMIT
|
||||
run: echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
##########################################################################
|
||||
# NEO4J ##################################################################
|
||||
##########################################################################
|
||||
- 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 -t "ocelotsocialnetwork/neo4j-community:latest" -t "ocelotsocialnetwork/neo4j-community:${VERSION}" -t "ocelotsocialnetwork/neo4j-community:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT neo4j/
|
||||
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
|
||||
@ -73,32 +45,41 @@ jobs:
|
||||
build_production_backend:
|
||||
name: Docker Build Production - Backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# SET ENVS ###############################################################
|
||||
##########################################################################
|
||||
- name: ENV - VERSION
|
||||
run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_DATE
|
||||
run: echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_VERSION
|
||||
run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_COMMIT
|
||||
run: echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
##########################################################################
|
||||
# BUILD BACKEND DOCKER IMAGE (production) ################################
|
||||
##########################################################################
|
||||
- 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 -t "ocelotsocialnetwork/backend:latest-base" -t "ocelotsocialnetwork/backend:${VERSION}-base" -t "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 -t "ocelotsocialnetwork/backend:latest-code" -t "ocelotsocialnetwork/backend:${VERSION}-code" -t "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 -t "ocelotsocialnetwork/backend:latest" -t "ocelotsocialnetwork/backend:${VERSION}" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
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
|
||||
@ -113,32 +94,41 @@ jobs:
|
||||
build_production_webapp:
|
||||
name: Docker Build Production - WebApp
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# SET ENVS ###############################################################
|
||||
##########################################################################
|
||||
- name: ENV - VERSION
|
||||
run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_DATE
|
||||
run: echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_VERSION
|
||||
run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_COMMIT
|
||||
run: echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
##########################################################################
|
||||
# BUILD WEBAPP DOCKER IMAGE (build) ######################################
|
||||
##########################################################################
|
||||
- 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 -t "ocelotsocialnetwork/webapp:latest-base" -t "ocelotsocialnetwork/webapp:${VERSION}-base" -t "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 -t "ocelotsocialnetwork/webapp:latest-code" -t "ocelotsocialnetwork/webapp:${VERSION}-code" -t "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 -t "ocelotsocialnetwork/webapp:latest" -t "ocelotsocialnetwork/webapp:${VERSION}" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
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
|
||||
@ -153,32 +143,44 @@ jobs:
|
||||
build_production_maintenance:
|
||||
name: Docker Build Production - Maintenance
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# SET ENVS ###############################################################
|
||||
##########################################################################
|
||||
- name: ENV - VERSION
|
||||
run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_DATE
|
||||
run: echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_VERSION
|
||||
run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_COMMIT
|
||||
run: echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
##########################################################################
|
||||
# BUILD MAINTENANCE DOCKER IMAGE (build) #################################
|
||||
##########################################################################
|
||||
- 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 -t "ocelotsocialnetwork/maintenance:latest-base" -t "ocelotsocialnetwork/maintenance:${VERSION}-base" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker build --target code -t "ocelotsocialnetwork/maintenance:latest-code" -t "ocelotsocialnetwork/maintenance:${VERSION}-code" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker build --target production -t "ocelotsocialnetwork/maintenance:latest" -t "ocelotsocialnetwork/maintenance:${VERSION}" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
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
|
||||
@ -198,130 +200,39 @@ jobs:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGES #################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Neo4J)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-neo4j-community
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/neo4j.tar
|
||||
- run: docker load < /tmp/neo4j.tar
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-backend-production
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/backend.tar
|
||||
- run: docker load < /tmp/backend.tar
|
||||
- name: Download Docker Image (WebApp)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-webapp-production
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/webapp.tar
|
||||
- run: docker load < /tmp/webapp.tar
|
||||
- name: Download Docker Image (Maintenance)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-maintenance-production
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/maintenance.tar
|
||||
##########################################################################
|
||||
# Upload #################################################################
|
||||
##########################################################################
|
||||
- run: docker load < /tmp/maintenance.tar
|
||||
|
||||
- name: login to dockerhub
|
||||
run: echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
|
||||
- name: Push neo4j
|
||||
run: docker push --all-tags ocelotsocialnetwork/neo4j-community
|
||||
- name: Push backend
|
||||
run: docker push --all-tags ocelotsocialnetwork/backend
|
||||
- name: Push webapp
|
||||
run: docker push --all-tags ocelotsocialnetwork/webapp
|
||||
- name: Push maintenance
|
||||
run: docker push --all-tags ocelotsocialnetwork/maintenance
|
||||
|
||||
##############################################################################
|
||||
# JOB: KUBERNETES DEPLOY ACTUAL/LATEST VERSION ######################################
|
||||
##############################################################################
|
||||
kubernetes_deploy:
|
||||
# see example https://github.com/do-community/example-doctl-action
|
||||
# see example https://github.com/do-community/example-doctl-action/blob/main/.github/workflows/workflow.yaml
|
||||
name: Kubernetes deploy of latest version to stage.ocelot.social cluster at DigitalOcean
|
||||
runs-on: ubuntu-latest
|
||||
needs: [upload_to_dockerhub]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# SET ENVS ###############################################################
|
||||
##########################################################################
|
||||
- name: ENV - VERSION
|
||||
run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_VERSION
|
||||
run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
##########################################################################
|
||||
# Install DigitalOceans doctl and set kubeconfig #########################
|
||||
##########################################################################
|
||||
- name: Install doctl
|
||||
uses: digitalocean/action-doctl@v2
|
||||
with:
|
||||
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
- name: Save DigitalOcean kubeconfig with short-lived credentials
|
||||
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 cluster-stage-ocelot-social
|
||||
##########################################################################
|
||||
# Deploy new Docker images to DigitalOcean Kubernetes cluster ############
|
||||
##########################################################################
|
||||
# - name: Deploy 'latest' to DigitalOcean Kubernetes
|
||||
# run: |
|
||||
# kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp:latest
|
||||
# kubectl -n default rollout restart deployment/ocelot-webapp
|
||||
# kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend:latest
|
||||
# kubectl -n default rollout restart deployment/ocelot-backend
|
||||
# kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance:latest
|
||||
# kubectl -n default rollout restart deployment/ocelot-maintenance
|
||||
# kubectl -n default set image deployment/ocelot-neo4j container-ocelot-neo4j=ocelotsocialnetwork/neo4j-community:latest
|
||||
# kubectl -n default rollout restart deployment/ocelot-neo4j
|
||||
- name: Deploy actual version '$BUILD_VERSION' to DigitalOcean Kubernetes
|
||||
- name: Push images
|
||||
run: |
|
||||
kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-webapp
|
||||
kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-backend
|
||||
kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-maintenance
|
||||
kubectl -n default set image deployment/ocelot-neo4j container-ocelot-neo4j=ocelotsocialnetwork/neo4j-community:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-neo4j
|
||||
# because this step 'kubectl -n default rollout status deployment/* --timeout=600s' does not work as expected
|
||||
# and we need the pods to be up again for cleaning and seeding the Neo4j database and the backend.
|
||||
# !!! this is not a perfect solution !!!
|
||||
# deployments are regularly up again after 3 minutes and 10 seconds
|
||||
- name: Sleep for 4 minutes, means 240 seconds
|
||||
run: sleep 240s
|
||||
shell: bash
|
||||
- name: Verify deployment and wait for the pods of each deployment to get ready for cleaning and seeding of the database
|
||||
run: |
|
||||
kubectl -n default rollout status deployment/ocelot-backend --timeout=600s
|
||||
kubectl -n default rollout status deployment/ocelot-neo4j --timeout=600s
|
||||
kubectl -n default rollout status deployment/ocelot-maintenance --timeout=600s
|
||||
kubectl -n default rollout status deployment/ocelot-webapp --timeout=600s
|
||||
- name: Run migrations for Neo4j database via backend for staging
|
||||
run: |
|
||||
kubectl -n default exec -it $(kubectl -n default get pods | grep ocelot-backend | awk '{ print $1 }') -- /bin/sh -c "yarn prod:migrate up"
|
||||
- name: Reset and seed Neo4j database via backend for staging
|
||||
# db cleaning and seeding is only possible in production if env 'PRODUCTION_DB_CLEAN_ALLOW=true' is set in deployment
|
||||
run: |
|
||||
kubectl -n default exec -it $(kubectl -n default get pods | grep ocelot-backend | awk '{ print $1 }') -- /bin/sh -c "node --experimental-repl-await dist/db/clean.js && node --experimental-repl-await dist/db/seed.js"
|
||||
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 #############################################
|
||||
@ -331,39 +242,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [upload_to_dockerhub]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Fetch full History for changelog
|
||||
##########################################################################
|
||||
# SET ENVS ###############################################################
|
||||
##########################################################################
|
||||
- name: ENV - VERSION
|
||||
run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_DATE
|
||||
run: echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_VERSION
|
||||
run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: ENV - BUILD_COMMIT
|
||||
run: echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
##########################################################################
|
||||
# Push version tag to GitHub #############################################
|
||||
##########################################################################
|
||||
# TODO: this will error on duplicate
|
||||
#- name: package-version-to-git-tag
|
||||
# uses: pkgdeps/git-tag-action@v2
|
||||
# with:
|
||||
# github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# github_repo: ${{ github.repository }}
|
||||
# version: ${{ env.VERSION }}
|
||||
# git_commit_sha: ${{ github.sha }}
|
||||
# git_tag_prefix: "v"
|
||||
##########################################################################
|
||||
# Push build tag to GitHub ###############################################
|
||||
##########################################################################
|
||||
- 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: package-version-to-git-tag + build number
|
||||
uses: pkgdeps/git-tag-action@v2
|
||||
with:
|
||||
@ -372,13 +260,10 @@ jobs:
|
||||
version: ${{ env.BUILD_VERSION }}
|
||||
git_commit_sha: ${{ github.sha }}
|
||||
git_tag_prefix: "b"
|
||||
##########################################################################
|
||||
# Push release tag to GitHub #############################################
|
||||
##########################################################################
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: generate changelog
|
||||
run: yarn auto-changelog --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
yarn install
|
||||
yarn auto-changelog --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
- name: package-version-to-git-release
|
||||
continue-on-error: true # Will fail if tag exists
|
||||
id: create_release
|
||||
@ -391,3 +276,101 @@ jobs:
|
||||
body_path: ./CHANGELOG.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
build_trigger:
|
||||
name: Trigger successful build
|
||||
runs-on: ubuntu-latest
|
||||
needs: [github_tag]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Fetch full History for changelog
|
||||
- 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: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
event-type: trigger-build-success
|
||||
repository: ${{ github.repository }}
|
||||
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
# ##############################################################################
|
||||
# # JOB: KUBERNETES DEPLOY ACTUAL/LATEST VERSION ######################################
|
||||
# ##############################################################################
|
||||
# kubernetes_deploy:
|
||||
# # see example https://github.com/do-community/example-doctl-action
|
||||
# # see example https://github.com/do-community/example-doctl-action/blob/main/.github/workflows/workflow.yaml
|
||||
# name: Kubernetes deploy of latest version to stage.ocelot.social cluster at DigitalOcean
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: [upload_to_dockerhub]
|
||||
# steps:
|
||||
# ##########################################################################
|
||||
# # CHECKOUT CODE ##########################################################
|
||||
# ##########################################################################
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v3
|
||||
# ##########################################################################
|
||||
# # SET ENVS ###############################################################
|
||||
# ##########################################################################
|
||||
# - name: ENV - VERSION
|
||||
# run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
# - name: ENV - BUILD_VERSION
|
||||
# run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
# ##########################################################################
|
||||
# # Install DigitalOceans doctl and set kubeconfig #########################
|
||||
# ##########################################################################
|
||||
# - name: Install doctl
|
||||
# uses: digitalocean/action-doctl@v2
|
||||
# with:
|
||||
# token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
# - name: Save DigitalOcean kubeconfig with short-lived credentials
|
||||
# run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 cluster-stage-ocelot-social
|
||||
# ##########################################################################
|
||||
# # Deploy new Docker images to DigitalOcean Kubernetes cluster ############
|
||||
# ##########################################################################
|
||||
# # - name: Deploy 'latest' to DigitalOcean Kubernetes
|
||||
# # run: |
|
||||
# # kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp:latest
|
||||
# # kubectl -n default rollout restart deployment/ocelot-webapp
|
||||
# # kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend:latest
|
||||
# # kubectl -n default rollout restart deployment/ocelot-backend
|
||||
# # kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance:latest
|
||||
# # kubectl -n default rollout restart deployment/ocelot-maintenance
|
||||
# # kubectl -n default set image deployment/ocelot-neo4j container-ocelot-neo4j=ocelotsocialnetwork/neo4j-community:latest
|
||||
# # kubectl -n default rollout restart deployment/ocelot-neo4j
|
||||
# - name: Deploy actual version '$BUILD_VERSION' to DigitalOcean Kubernetes
|
||||
# run: |
|
||||
# kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp:$BUILD_VERSION
|
||||
# kubectl -n default rollout restart deployment/ocelot-webapp
|
||||
# kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend:$BUILD_VERSION
|
||||
# kubectl -n default rollout restart deployment/ocelot-backend
|
||||
# kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance:$BUILD_VERSION
|
||||
# kubectl -n default rollout restart deployment/ocelot-maintenance
|
||||
# kubectl -n default set image deployment/ocelot-neo4j container-ocelot-neo4j=ocelotsocialnetwork/neo4j-community:$BUILD_VERSION
|
||||
# kubectl -n default rollout restart deployment/ocelot-neo4j
|
||||
# # because this step 'kubectl -n default rollout status deployment/* --timeout=600s' does not work as expected
|
||||
# # and we need the pods to be up again for cleaning and seeding the Neo4j database and the backend.
|
||||
# # !!! this is not a perfect solution !!!
|
||||
# # deployments are regularly up again after 3 minutes and 10 seconds
|
||||
# - name: Sleep for 4 minutes, means 240 seconds
|
||||
# run: sleep 240s
|
||||
# shell: bash
|
||||
# - name: Verify deployment and wait for the pods of each deployment to get ready for cleaning and seeding of the database
|
||||
# run: |
|
||||
# kubectl -n default rollout status deployment/ocelot-backend --timeout=600s
|
||||
# kubectl -n default rollout status deployment/ocelot-neo4j --timeout=600s
|
||||
# kubectl -n default rollout status deployment/ocelot-maintenance --timeout=600s
|
||||
# kubectl -n default rollout status deployment/ocelot-webapp --timeout=600s
|
||||
# - name: Run migrations for Neo4j database via backend for staging
|
||||
# run: |
|
||||
# kubectl -n default exec -it $(kubectl -n default get pods | grep ocelot-backend | awk '{ print $1 }') -- /bin/sh -c "yarn prod:migrate up"
|
||||
# - name: Reset and seed Neo4j database via backend for staging
|
||||
# # db cleaning and seeding is only possible in production if env 'PRODUCTION_DB_CLEAN_ALLOW=true' is set in deployment
|
||||
# run: |
|
||||
# kubectl -n default exec -it $(kubectl -n default get pods | grep ocelot-backend | awk '{ print $1 }') -- /bin/sh -c "node --experimental-repl-await dist/db/clean.js && node --experimental-repl-await dist/db/seed.js"
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@ -168,6 +168,8 @@ jobs:
|
||||
name: Unit tests - backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_neo4j,build_test_backend]
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -225,6 +227,8 @@ jobs:
|
||||
name: Unit tests - webapp
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_webapp]
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -269,7 +273,7 @@ jobs:
|
||||
report_name: Coverage Webapp
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 63
|
||||
min_coverage: 83
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
@ -174,34 +174,14 @@ Please copy and paste the following quotes for the languages:
|
||||
|
||||
#### Environment Variable For Apple M1 Platform
|
||||
|
||||
To set the Docker platform environment variable in your terminal tab, run:
|
||||
If you encounter trouble building the docker containers on an Apple M1 chip you can try to explicitly define the target platform docker builds and pulls images for:
|
||||
|
||||
```bash
|
||||
# set env variable for your shell
|
||||
$ export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
||||
```
|
||||
|
||||
#### Docker Compose Override File For Apple M1 Platform
|
||||
|
||||
For Docker compose `up` or `build` commands, you can use our Apple M1 override file that specifies the M1 platform:
|
||||
|
||||
```bash
|
||||
# in main folder
|
||||
|
||||
# for development
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.apple-m1.override.yml up
|
||||
# only once: init admin user and create indexes and contraints in Neo4j database
|
||||
$ docker compose exec backend yarn prod:migrate init
|
||||
# clean db
|
||||
$ docker compose exec backend yarn db:reset
|
||||
# seed db
|
||||
$ docker compose exec backend yarn db:seed
|
||||
|
||||
# for production
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.apple-m1.override.yml up
|
||||
# only once: init admin user and create indexes and contraints in Neo4j database
|
||||
$ docker compose exec backend /bin/sh -c "yarn prod:migrate init"
|
||||
```
|
||||
Or alternatively use a `YAML` docker compose overwrite file with `platform: linux/amd64`.
|
||||
|
||||
### Analyzing Docker Builds
|
||||
|
||||
|
||||
93
README.md
93
README.md
@ -20,12 +20,71 @@ At the same time, it should be possible in the future to link these networks wit
|
||||
|
||||
In other words, we are interested in a network of networks and in keeping the data as close as possible to the user and the operator they trusts.
|
||||
|
||||
## Introduction
|
||||
## Screenshots
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/17728384/218597429-554e4082-3906-4721-8f68-0c13146fc218.png" alt="Post feed" title="Post feed" />
|
||||
|
||||
Check out more screenshots [here](/wiki/en:Screenshots).
|
||||
|
||||
## Features
|
||||
|
||||
Ocelot.social networks feature:
|
||||
* <strong>user accounts</strong>
|
||||
* <strong>user roles</strong>
|
||||
* <strong>posts</strong> and <strong>comments</strong>
|
||||
* <strong>groups</strong>
|
||||
* <strong>maps</strong>
|
||||
* <strong>search</strong>
|
||||
* <strong>filters</strong>
|
||||
* and more …
|
||||
|
||||
Check out the [full feature list](/wiki/en:FAQ#what-are-the-features).
|
||||
|
||||
## User Guide and Frequently Asked Questions
|
||||
|
||||
In the [wiki](/wiki) you can find more information.
|
||||
|
||||
* [User Guide](/wiki/en:User-Guide)
|
||||
* [Frequently Asked Questions](/wiki/en:FAQ)
|
||||
|
||||
## Demo
|
||||
|
||||
Try out our live demo network, see [here](#live-demo-and-developer-logins).
|
||||
|
||||
## Help us
|
||||
|
||||
If you're wondering how you could help, there are plenty of ways, e.g.:
|
||||
|
||||
* Spread the good word about ocelot.social to make it more popular:
|
||||
* Add the link [ocelot.social](https://ocelot.social) to your website.
|
||||
* Give ocelot.social a Like at https://alternativeto.net/software/ocelot-social/.
|
||||
* Star our project on GitHub at https://github.com/Ocelot-Social-Community/Ocelot-Social/.
|
||||
* Promote it on your social networks.
|
||||
* Tell your friends about it by word-of-mouth.
|
||||
* Write a press article on ocelot.social or contact the editorial office of your local newspage or radio station.
|
||||
* Take a [good first issue](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) or issues that need help. Make sure to read [CONTRIBUTING.md](/CONTRIBUTING.md) first though.
|
||||
* Testing and [reporting](/issues/new/choose) bugs.
|
||||
* Translating: Please [contact us](#contact).
|
||||
* Reviewing and testing pull requests.
|
||||
|
||||
## Donate
|
||||
|
||||
Your donation is very welcome and helps to enhance and improve the network. This software is mostly developped and maintained by the association [busFaktor() e.V.](https://www.busfaktor.org/en). Please support us with a [donation](https://www.busfaktor.org/en/donations) to busFaktor() e.V.. Thanks a lot! ❤️
|
||||
|
||||
## Contact
|
||||
|
||||
Are you interested in operating your own ocelot.social network or becoming a user? Please contact us here:
|
||||
* [hello@ocelot.social](mailto:hello@ocelot.social)
|
||||
* [Discord](https://discord.com/invite/DFSjPaX)
|
||||
|
||||
## For Developers and Contributors
|
||||
|
||||
### Introduction
|
||||
|
||||
Have a look into our short video:
|
||||
[ocelot.social - GitHub - Developer Welcome - Tutorial (english)](https://www.youtube.com/watch?v=gZSL6KvBIiY&list=PLFMD5liPP01kbuReHxYXxv_1fI5rIgS1f&index=1)
|
||||
|
||||
## Directory Layout
|
||||
### Directory Layout
|
||||
|
||||
There are three important directories:
|
||||
|
||||
@ -41,9 +100,9 @@ There are two approaches:
|
||||
1. [Local](#local-installation) installation, which means you have to take care of dependencies yourself.
|
||||
2. **Or** Install everything through [Docker](#docker-installation) which takes care of dependencies for you.
|
||||
|
||||
## Installation
|
||||
### Installation
|
||||
|
||||
### Clone the Repository
|
||||
#### Clone the Repository
|
||||
|
||||
Clone the repository, this will create a new folder called `Ocelot-Social`:
|
||||
|
||||
@ -65,7 +124,7 @@ Change into the new folder.
|
||||
$ cd Ocelot-Social
|
||||
```
|
||||
|
||||
## Live Demo And Developer Logins
|
||||
### Live Demo And Developer Logins
|
||||
|
||||
**Try out our deployed [development environment](https://stage.ocelot.social).**
|
||||
|
||||
@ -74,7 +133,7 @@ Visit our staging networks:
|
||||
* central staging network: [stage.ocelot.social](https://stage.ocelot.social)
|
||||
<!-- - rebranded staging network: [rebrand.ocelot.social](https://stage.ocelot.social). -->
|
||||
|
||||
### Login
|
||||
#### Login
|
||||
|
||||
Logins for the live demos and developers (local developers after the following installations) in the browser:
|
||||
|
||||
@ -84,11 +143,11 @@ Logins for the live demos and developers (local developers after the following i
|
||||
| `moderator@example.org` | 1234 | moderator |
|
||||
| `admin@example.org` | 1234 | admin |
|
||||
|
||||
### Docker Installation
|
||||
#### Docker Installation
|
||||
|
||||
Docker is a software development container tool that combines software and its dependencies into one standardized unit that contains everything needed to run it. This helps us to avoid problems with dependencies and makes installation easier.
|
||||
|
||||
#### General Installation of Docker
|
||||
##### General Installation of Docker
|
||||
|
||||
There are [several ways to install Docker CE](https://docs.docker.com/install/) on your computer or server.
|
||||
|
||||
@ -105,7 +164,7 @@ $ docker-compose --version
|
||||
docker-compose version 1.23.2
|
||||
```
|
||||
|
||||
#### Start Ocelot-Social via Docker-Compose
|
||||
##### Start Ocelot-Social via Docker-Compose
|
||||
|
||||
Prepare ENVs once beforehand:
|
||||
|
||||
@ -152,11 +211,11 @@ $ docker-compose exec backend yarn run db:seed
|
||||
For a closer description see [backend README.md](./backend/README.md).
|
||||
For a full documentation see [SUMMARY](./SUMMARY.md).
|
||||
|
||||
### Local Installation
|
||||
#### Local Installation
|
||||
|
||||
For a full documentation see [SUMMARY](./SUMMARY.md).
|
||||
|
||||
## Contributing
|
||||
### Contributing
|
||||
|
||||
Choose an issue (consider our label [good-first-issue](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)) and leave a comment there. We will then invite you to join our volunteers team.
|
||||
To have the necessary permission to push directly to this repository, please accept our invitation to join our volunteers team, you will receive via the email, Github will send you, once invited. If we did not invite you yet, please request an invitation via Discord.
|
||||
@ -182,7 +241,7 @@ $ yarn test
|
||||
|
||||
Check out our [contribution guideline](./CONTRIBUTING.md), too!
|
||||
|
||||
### Developer Chat
|
||||
#### Developer Chat
|
||||
|
||||
Join our friendly open-source community on [Discord](https://discord.gg/AJSX9DCSUA) :heart_eyes_cat:
|
||||
Just introduce yourself at `#introduce-yourself` and mention a mentor or `@@Mentors` to get you onboard :neckbeard:
|
||||
@ -190,13 +249,13 @@ Just introduce yourself at `#introduce-yourself` and mention a mentor or `@@Ment
|
||||
We give write permissions to every developer who asks for it. Just text us on
|
||||
[Discord](https://discord.gg/AJSX9DCSUA).
|
||||
|
||||
## Deployment
|
||||
### Deployment
|
||||
|
||||
Deployment methods can be found in the [Ocelot-Social-Deploy-Rebranding](https://github.com/Ocelot-Social-Community/Ocelot-Social-Deploy-Rebranding) repository.
|
||||
|
||||
The only deployment method in this repository for development purposes as described above is `docker-compose`.
|
||||
|
||||
## Technology Stack
|
||||
### Technology Stack
|
||||
|
||||
* [VueJS](https://vuejs.org/)
|
||||
* [NuxtJS](https://nuxtjs.org/)
|
||||
@ -204,7 +263,7 @@ The only deployment method in this repository for development purposes as descri
|
||||
* [NodeJS](https://nodejs.org/en/)
|
||||
* [Neo4J](https://neo4j.com/)
|
||||
|
||||
### For Testing
|
||||
#### For Testing
|
||||
|
||||
* [Cypress](https://docs.cypress.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
@ -212,7 +271,7 @@ The only deployment method in this repository for development purposes as descri
|
||||
* [Vue Test Utils](https://vue-test-utils.vuejs.org/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## Attributions
|
||||
### Attributions
|
||||
|
||||
Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
|
||||
|
||||
@ -220,6 +279,6 @@ Browser compatibility testing with [BrowserStack](https://www.browserstack.com/)
|
||||
|
||||
<img alt="BrowserStack Logo" src=".gitbook/assets/browserstack-logo.svg" width="256">
|
||||
|
||||
## License
|
||||
### License
|
||||
|
||||
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
|
||||
|
||||
@ -79,9 +79,9 @@ More details about our GraphQL playground and how to use it with ocelot.social c
|
||||
|
||||

|
||||
|
||||
### Database Indices and Constraints
|
||||
### Database Indexes and Constraints
|
||||
|
||||
Database indices and constraints need to be created when the database and the
|
||||
Database indexes and constraints need to be created when the database and the
|
||||
backend is running:
|
||||
|
||||
{% tabs %}
|
||||
@ -90,6 +90,12 @@ backend is running:
|
||||
```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"
|
||||
```
|
||||
|
||||
{% endtab %}
|
||||
@ -126,7 +132,7 @@ To reset the database run:
|
||||
$ docker exec backend yarn run db:reset
|
||||
# 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 indeces and contstraints
|
||||
# if container is not running, run this command to set up your database indexes and constraints
|
||||
$ docker exec backend yarn run db:migrate init
|
||||
```
|
||||
|
||||
|
||||
@ -65,7 +65,6 @@ Factory.define('basicUser')
|
||||
name: faker.name.findName,
|
||||
password: '1234',
|
||||
role: 'user',
|
||||
about: faker.lorem.paragraph,
|
||||
termsAndConditionsAgreedVersion: '0.0.1',
|
||||
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
|
||||
allowEmbedIframes: false,
|
||||
@ -82,12 +81,28 @@ Factory.define('basicUser')
|
||||
|
||||
Factory.define('userWithoutEmailAddress')
|
||||
.extend('basicUser')
|
||||
.option('about', faker.lorem.paragraph)
|
||||
.after(async (buildObject, options) => {
|
||||
return neode.create('User', buildObject)
|
||||
})
|
||||
|
||||
Factory.define('userWithAboutNull')
|
||||
.extend('basicUser')
|
||||
.option('about', null)
|
||||
.after(async (buildObject, options) => {
|
||||
return neode.create('User', buildObject)
|
||||
})
|
||||
|
||||
Factory.define('userWithAboutEmpty')
|
||||
.extend('basicUser')
|
||||
.option('about', '')
|
||||
.after(async (buildObject, options) => {
|
||||
return neode.create('User', buildObject)
|
||||
})
|
||||
|
||||
Factory.define('user')
|
||||
.extend('basicUser')
|
||||
.option('about', faker.lorem.paragraph)
|
||||
.option('email', faker.internet.exampleEmail)
|
||||
.option('avatar', () =>
|
||||
Factory.build('image', {
|
||||
|
||||
@ -150,6 +150,19 @@ export const changeGroupMemberRoleMutation = () => {
|
||||
`
|
||||
}
|
||||
|
||||
export const removeUserFromGroupMutation = () => {
|
||||
return gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
RemoveUserFromGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
// ------ queries
|
||||
|
||||
export const groupQuery = () => {
|
||||
|
||||
65
backend/src/graphql/notifications.js
Normal file
65
backend/src/graphql/notifications.js
Normal file
@ -0,0 +1,65 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
// ------ mutations
|
||||
|
||||
export const markAsReadMutation = () => {
|
||||
return gql`
|
||||
mutation ($id: ID!) {
|
||||
markAsRead(id: $id) {
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
read
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const markAllAsReadMutation = () => {
|
||||
return gql`
|
||||
mutation {
|
||||
markAllAsRead {
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
read
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
// ------ queries
|
||||
|
||||
export const notificationQuery = () => {
|
||||
return gql`
|
||||
query ($read: Boolean, $orderBy: NotificationOrdering) {
|
||||
notifications(read: $read, orderBy: $orderBy) {
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
read
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
@ -1,13 +1,3 @@
|
||||
// TODO: can be replaced with: (which is no a fake)
|
||||
// import gql from 'graphql-tag'
|
||||
// See issue: https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/5152
|
||||
|
||||
//* This is a fake ES2015 template string, just to benefit of syntax
|
||||
// highlighting of `gql` template strings in certain editors.
|
||||
export function gql(strings) {
|
||||
return strings.join('')
|
||||
}
|
||||
|
||||
// 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,4 +1,4 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { cleanDatabase } from '../../db/factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { cleanDatabase } from '../../db/factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { gql } from '../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { cleanDatabase } from '../db/factories'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -253,6 +253,42 @@ const isMemberOfGroup = rule({
|
||||
}
|
||||
})
|
||||
|
||||
const canRemoveUserFromGroup = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
if (!(user && user.id)) return false
|
||||
const { groupId, userId } = args
|
||||
const currentUserId = user.id
|
||||
if (currentUserId === userId) return false
|
||||
const session = driver.session()
|
||||
const readTxPromise = session.readTransaction(async (transaction) => {
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (User {id: $currentUserId})-[currentUserMembership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||
OPTIONAL MATCH (group)<-[userMembership:MEMBER_OF]-(user:User { id: $userId })
|
||||
RETURN currentUserMembership.role AS currentUserRole, userMembership.role AS userRole
|
||||
`,
|
||||
{ currentUserId, groupId, userId },
|
||||
)
|
||||
return {
|
||||
currentUserRole: transactionResponse.records.map((record) =>
|
||||
record.get('currentUserRole'),
|
||||
)[0],
|
||||
userRole: transactionResponse.records.map((record) => record.get('userRole'))[0],
|
||||
}
|
||||
})
|
||||
try {
|
||||
const { currentUserRole, userRole } = await readTxPromise
|
||||
return (
|
||||
currentUserRole && ['owner'].includes(currentUserRole) && userRole && userRole !== 'owner'
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const canCommentPost = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
@ -382,6 +418,7 @@ export default shield(
|
||||
JoinGroup: isAllowedToJoinGroup,
|
||||
LeaveGroup: isAllowedToLeaveGroup,
|
||||
ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole,
|
||||
RemoveUserFromGroup: canRemoveUserFromGroup,
|
||||
CreatePost: and(isAuthenticated, isMemberOfGroup),
|
||||
UpdatePost: isAuthor,
|
||||
DeletePost: isAuthor,
|
||||
@ -412,6 +449,7 @@ export default shield(
|
||||
blockUser: isAuthenticated,
|
||||
unblockUser: isAuthenticated,
|
||||
markAsRead: isAuthenticated,
|
||||
markAllAsRead: isAuthenticated,
|
||||
AddEmailAddress: isAuthenticated,
|
||||
VerifyEmailAddress: isAuthenticated,
|
||||
pinPost: isAdmin,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import { gql } from '../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getDriver, getNeode } from '../db/neo4j'
|
||||
import CONFIG from '../config'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import { gql } from '../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import createServer from '../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -3,7 +3,7 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
jest.mock('node-fetch')
|
||||
const { Response } = jest.requireActual('node-fetch')
|
||||
|
||||
@ -2,7 +2,7 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
@ -295,25 +295,8 @@ export default {
|
||||
LeaveGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
const { groupId, userId } = params
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const leaveGroupCypher = `
|
||||
MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||
DELETE membership
|
||||
WITH member, group
|
||||
OPTIONAL MATCH (p:Post)-[:IN]->(group)
|
||||
WHERE NOT group.groupType = 'public'
|
||||
WITH member, group, collect(p) AS posts
|
||||
FOREACH (post IN posts |
|
||||
MERGE (member)-[:CANNOT_SEE]->(post))
|
||||
RETURN member {.*, myRoleInGroup: NULL}
|
||||
`
|
||||
|
||||
const transactionResponse = await transaction.run(leaveGroupCypher, { groupId, userId })
|
||||
const [member] = await transactionResponse.records.map((record) => record.get('member'))
|
||||
return member
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
@ -368,6 +351,17 @@ export default {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
RemoveUserFromGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
const { groupId, userId } = params
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
Group: {
|
||||
...Resolver('Group', {
|
||||
@ -383,3 +377,27 @@ export default {
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId) => {
|
||||
return session.writeTransaction(async (transaction) => {
|
||||
const removeUserFromGroupCypher = `
|
||||
MATCH (user:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||
DELETE membership
|
||||
WITH user, group
|
||||
OPTIONAL MATCH (author:User)-[:WROTE]->(p:Post)-[:IN]->(group)
|
||||
WHERE NOT group.groupType = 'public'
|
||||
AND NOT author.id = $userId
|
||||
WITH user, collect(p) AS posts
|
||||
FOREACH (post IN posts |
|
||||
MERGE (user)-[:CANNOT_SEE]->(post))
|
||||
RETURN user {.*, myRoleInGroup: NULL}
|
||||
`
|
||||
|
||||
const transactionResponse = await transaction.run(removeUserFromGroupCypher, {
|
||||
groupId,
|
||||
userId,
|
||||
})
|
||||
const [user] = await transactionResponse.records.map((record) => record.get('user'))
|
||||
return user
|
||||
})
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
joinGroupMutation,
|
||||
leaveGroupMutation,
|
||||
changeGroupMemberRoleMutation,
|
||||
removeUserFromGroupMutation,
|
||||
groupMembersQuery,
|
||||
groupQuery,
|
||||
} from '../../graphql/groups'
|
||||
@ -196,7 +197,6 @@ const seedComplexScenarioAndClearAuthentication = async () => {
|
||||
},
|
||||
})
|
||||
// hidden-group
|
||||
authenticatedUser = await adminMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
@ -214,32 +214,17 @@ const seedComplexScenarioAndClearAuthentication = async () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'second-owner-member-user',
|
||||
userId: 'usual-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'second-owner-member-user',
|
||||
roleInGroup: 'usual',
|
||||
roleInGroup: 'admin',
|
||||
},
|
||||
})
|
||||
|
||||
@ -2982,4 +2967,192 @@ describe('in mode', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('RemoveUserFromGroup', () => {
|
||||
beforeAll(async () => {
|
||||
await seedComplexScenarioAndClearAuthentication()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'usual-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
describe('as usual member', () => {
|
||||
it('throws an error', async () => {
|
||||
authenticatedUser = await usualMemberUser.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as owner', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
})
|
||||
|
||||
it('removes the user from the group', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'usual-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
RemoveUserFromGroup: expect.objectContaining({
|
||||
id: 'usual-member-user',
|
||||
myRoleInGroup: null,
|
||||
}),
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('cannot remove self', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'owner-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as admin', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await adminMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'usual-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
authenticatedUser = await usualMemberUser.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
it('removes the user from the group', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'usual-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
RemoveUserFromGroup: expect.objectContaining({
|
||||
id: 'usual-member-user',
|
||||
myRoleInGroup: null,
|
||||
}),
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('cannot remove self', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
|
||||
it('cannot remove owner', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'owner-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
*/
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import { mergeWith, isArray } from 'lodash'
|
||||
|
||||
const getMyGroupIds = async (context) => {
|
||||
const { user } = context
|
||||
if (!(user && user.id)) return []
|
||||
const session = context.driver.session()
|
||||
|
||||
const readTxResultPromise = await session.readTransaction(async (transaction) => {
|
||||
const cypher = `
|
||||
MATCH (group:Group)<-[membership:MEMBER_OF]-(:User { id: $userId })
|
||||
WHERE membership.role IN ['usual', 'admin', 'owner']
|
||||
RETURN collect(group.id) AS myGroupIds`
|
||||
const getMyGroupIdsResponse = await transaction.run(cypher, { userId: user.id })
|
||||
return getMyGroupIdsResponse.records.map((record) => record.get('myGroupIds'))
|
||||
})
|
||||
try {
|
||||
const [myGroupIds] = readTxResultPromise
|
||||
return myGroupIds
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export const filterPostsOfMyGroups = async (params, context) => {
|
||||
if (!(params.filter && params.filter.postsInMyGroups)) return params
|
||||
delete params.filter.postsInMyGroups
|
||||
const myGroupIds = await getMyGroupIds(context)
|
||||
params.filter = mergeWith(
|
||||
params.filter,
|
||||
{
|
||||
group: { id_in: myGroupIds },
|
||||
},
|
||||
(objValue, srcValue) => {
|
||||
if (isArray(objValue)) {
|
||||
return objValue.concat(srcValue)
|
||||
}
|
||||
},
|
||||
)
|
||||
return params
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import CONSTANTS_REGISTRATION from './../../constants/registration'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -99,6 +99,35 @@ export default {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
markAllAsRead: async (parent, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const markAllNotificationAsReadTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (resource)-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
|
||||
SET notification.read = TRUE
|
||||
WITH user, notification, resource,
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
|
||||
WITH resource, user, notification, authors, posts,
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
|
||||
RETURN notification {.*, from: finalResource, to: properties(user)}
|
||||
`,
|
||||
{ id: currentUser.id },
|
||||
)
|
||||
log(markAllNotificationAsReadTransactionResponse)
|
||||
return markAllNotificationAsReadTransactionResponse.records.map((record) =>
|
||||
record.get('notification'),
|
||||
)
|
||||
})
|
||||
try {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
NOTIFIED: {
|
||||
id: async (parent) => {
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../.././server'
|
||||
import {
|
||||
markAsReadMutation,
|
||||
markAllAsReadMutation,
|
||||
notificationQuery,
|
||||
} from '../../graphql/notifications'
|
||||
|
||||
const driver = getDriver()
|
||||
let authenticatedUser
|
||||
@ -146,26 +151,9 @@ describe('given some notifications', () => {
|
||||
})
|
||||
|
||||
describe('notifications', () => {
|
||||
const notificationQuery = gql`
|
||||
query ($read: Boolean, $orderBy: NotificationOrdering) {
|
||||
notifications(read: $read, orderBy: $orderBy) {
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
read
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await query({ query: notificationQuery })
|
||||
const { errors } = await query({ query: notificationQuery() })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
@ -212,7 +200,7 @@ describe('given some notifications', () => {
|
||||
},
|
||||
]
|
||||
|
||||
await expect(query({ query: notificationQuery, variables })).resolves.toMatchObject({
|
||||
await expect(query({ query: notificationQuery(), variables })).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: expect.arrayContaining(expected),
|
||||
},
|
||||
@ -246,7 +234,7 @@ describe('given some notifications', () => {
|
||||
},
|
||||
})
|
||||
const response = await query({
|
||||
query: notificationQuery,
|
||||
query: notificationQuery(),
|
||||
variables: { ...variables, read: false },
|
||||
})
|
||||
await expect(response).toMatchObject(expected)
|
||||
@ -275,14 +263,14 @@ describe('given some notifications', () => {
|
||||
|
||||
it('reduces notifications list', async () => {
|
||||
await expect(
|
||||
query({ query: notificationQuery, variables: { ...variables, read: false } }),
|
||||
query({ query: notificationQuery(), variables: { ...variables, read: false } }),
|
||||
).resolves.toMatchObject({
|
||||
data: { notifications: [expect.any(Object), expect.any(Object)] },
|
||||
errors: undefined,
|
||||
})
|
||||
await deletePostAction()
|
||||
await expect(
|
||||
query({ query: notificationQuery, variables: { ...variables, read: false } }),
|
||||
query({ query: notificationQuery(), variables: { ...variables, read: false } }),
|
||||
).resolves.toMatchObject({ data: { notifications: [] }, errors: undefined })
|
||||
})
|
||||
})
|
||||
@ -291,27 +279,10 @@ describe('given some notifications', () => {
|
||||
})
|
||||
|
||||
describe('markAsRead', () => {
|
||||
const markAsReadMutation = gql`
|
||||
mutation ($id: ID!) {
|
||||
markAsRead(id: $id) {
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
read
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const result = await mutate({
|
||||
mutation: markAsReadMutation,
|
||||
mutation: markAsReadMutation(),
|
||||
variables: { ...variables, id: 'p1' },
|
||||
})
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
@ -332,7 +303,7 @@ describe('given some notifications', () => {
|
||||
})
|
||||
|
||||
it('returns null', async () => {
|
||||
const response = await mutate({ mutation: markAsReadMutation, variables })
|
||||
const response = await mutate({ mutation: markAsReadMutation(), variables })
|
||||
expect(response.data.markAsRead).toEqual(null)
|
||||
expect(response.errors).toBeUndefined()
|
||||
})
|
||||
@ -348,7 +319,7 @@ describe('given some notifications', () => {
|
||||
})
|
||||
|
||||
it('updates `read` attribute and returns NOTIFIED relationship', async () => {
|
||||
const { data } = await mutate({ mutation: markAsReadMutation, variables })
|
||||
const { data } = await mutate({ mutation: markAsReadMutation(), variables })
|
||||
expect(data).toEqual({
|
||||
markAsRead: {
|
||||
from: {
|
||||
@ -369,7 +340,7 @@ describe('given some notifications', () => {
|
||||
}
|
||||
})
|
||||
it('returns null', async () => {
|
||||
const response = await mutate({ mutation: markAsReadMutation, variables })
|
||||
const response = await mutate({ mutation: markAsReadMutation(), variables })
|
||||
expect(response.data.markAsRead).toEqual(null)
|
||||
expect(response.errors).toBeUndefined()
|
||||
})
|
||||
@ -385,7 +356,7 @@ describe('given some notifications', () => {
|
||||
})
|
||||
|
||||
it('updates `read` attribute and returns NOTIFIED relationship', async () => {
|
||||
const { data } = await mutate({ mutation: markAsReadMutation, variables })
|
||||
const { data } = await mutate({ mutation: markAsReadMutation(), variables })
|
||||
expect(data).toEqual({
|
||||
markAsRead: {
|
||||
from: {
|
||||
@ -401,4 +372,46 @@ describe('given some notifications', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('markAllAsRead', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const result = await mutate({
|
||||
mutation: markAllAsReadMutation(),
|
||||
})
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
describe('not being notified at all', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
}
|
||||
})
|
||||
|
||||
it('returns all as read', async () => {
|
||||
const response = await mutate({ mutation: markAllAsReadMutation(), variables })
|
||||
expect(response.data.markAllAsRead).toEqual([
|
||||
{
|
||||
createdAt: '2019-08-30T19:33:48.651Z',
|
||||
from: { __typename: 'Comment', content: 'You have been mentioned in a comment' },
|
||||
read: true,
|
||||
},
|
||||
{
|
||||
createdAt: '2019-08-31T17:33:48.651Z',
|
||||
from: { __typename: 'Post', content: 'You have been mentioned in a post' },
|
||||
read: true,
|
||||
},
|
||||
])
|
||||
expect(response.errors).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import CONSTANTS_REGISTRATION from './../../constants/registration'
|
||||
import createPasswordReset from './helpers/createPasswordReset'
|
||||
|
||||
@ -6,6 +6,7 @@ import { mergeImage, deleteImage } from './images/images'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import { filterForMutedUsers } from './helpers/filterForMutedUsers'
|
||||
import { filterInvisiblePosts } from './helpers/filterInvisiblePosts'
|
||||
import { filterPostsOfMyGroups } from './helpers/filterPostsOfMyGroups'
|
||||
import CONFIG from '../../config'
|
||||
|
||||
const maintainPinnedPosts = (params) => {
|
||||
@ -21,12 +22,14 @@ const maintainPinnedPosts = (params) => {
|
||||
export default {
|
||||
Query: {
|
||||
Post: async (object, params, context, resolveInfo) => {
|
||||
params = await filterPostsOfMyGroups(params, context)
|
||||
params = await filterInvisiblePosts(params, context)
|
||||
params = await filterForMutedUsers(params, context)
|
||||
params = await maintainPinnedPosts(params)
|
||||
return neo4jgraphql(object, params, context, resolveInfo)
|
||||
},
|
||||
profilePagePosts: async (object, params, context, resolveInfo) => {
|
||||
params = await filterPostsOfMyGroups(params, context)
|
||||
params = await filterInvisiblePosts(params, context)
|
||||
params = await filterForMutedUsers(params, context)
|
||||
return neo4jgraphql(object, params, context, resolveInfo)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -1524,9 +1524,9 @@ describe('Posts in Groups', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not show the posts of the closed group anymore', async () => {
|
||||
it('stil shows the posts of the closed group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(3)
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1540,6 +1540,11 @@ describe('Posts in Groups', () => {
|
||||
title: 'A post without a group',
|
||||
content: 'I am a user who does not belong to a group yet.',
|
||||
},
|
||||
{
|
||||
id: 'post-to-closed-group',
|
||||
title: 'A post to a closed group',
|
||||
content: 'I am posting into a closed group as a member of the group',
|
||||
},
|
||||
{
|
||||
id: 'post-to-hidden-group',
|
||||
title: 'A post to a hidden group',
|
||||
@ -1564,9 +1569,9 @@ describe('Posts in Groups', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does only show the public posts', async () => {
|
||||
it('still shows the post of the hidden group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1580,6 +1585,16 @@ describe('Posts in Groups', () => {
|
||||
title: 'A post without a group',
|
||||
content: 'I am a user who does not belong to a group yet.',
|
||||
},
|
||||
{
|
||||
id: 'post-to-closed-group',
|
||||
title: 'A post to a closed group',
|
||||
content: 'I am posting into a closed group as a member of the group',
|
||||
},
|
||||
{
|
||||
id: 'post-to-hidden-group',
|
||||
title: 'A post to a hidden group',
|
||||
content: 'I am posting into a hidden group as a member of the group',
|
||||
},
|
||||
]),
|
||||
},
|
||||
errors: undefined,
|
||||
@ -1603,9 +1618,9 @@ describe('Posts in Groups', () => {
|
||||
authenticatedUser = await allGroupsUser.toJson()
|
||||
})
|
||||
|
||||
it('does not show the posts of the closed group', async () => {
|
||||
it('shows the posts of the closed group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(3)
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1624,6 +1639,11 @@ describe('Posts in Groups', () => {
|
||||
title: 'A post to a closed group',
|
||||
content: 'I am posting into a closed group as a member of the group',
|
||||
},
|
||||
{
|
||||
id: 'post-to-hidden-group',
|
||||
title: 'A post to a hidden group',
|
||||
content: 'I am posting into a hidden group as a member of the group',
|
||||
},
|
||||
]),
|
||||
},
|
||||
errors: undefined,
|
||||
@ -1678,5 +1698,59 @@ describe('Posts in Groups', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('filter posts in my groups', () => {
|
||||
describe('without any posts in groups', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await anyUser.toJson()
|
||||
})
|
||||
|
||||
it('finds no posts', async () => {
|
||||
const result = await query({
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postsInMyGroups: true } },
|
||||
})
|
||||
expect(result.data.Post).toHaveLength(0)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with posts in groups', () => {
|
||||
beforeAll(async () => {
|
||||
// member of hidden-group and closed-group
|
||||
authenticatedUser = await allGroupsUser.toJson()
|
||||
})
|
||||
|
||||
it('finds two posts', async () => {
|
||||
const result = await query({
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postsInMyGroups: true } },
|
||||
})
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
{
|
||||
id: 'post-to-closed-group',
|
||||
title: 'A post to a closed group',
|
||||
content: 'I am posting into a closed group as a member of the group',
|
||||
},
|
||||
{
|
||||
id: 'post-to-hidden-group',
|
||||
title: 'A post to a hidden group',
|
||||
content: 'I am posting into a hidden group as a member of the group',
|
||||
},
|
||||
]),
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../.././server'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
|
||||
const instance = getNeode()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from './../../config'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { loginMutation } from '../../graphql/userManagement'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer, { context } from '../../server'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { gql } from '../../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import Factory, { cleanDatabase } from '../../../db/factories'
|
||||
import { getNeode, getDriver } from '../../../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../../server'
|
||||
import { cleanDatabase } from '../../../db/factories'
|
||||
import { gql } from '../../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../../db/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import gql from 'graphql-tag'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -132,4 +132,9 @@ type Mutation {
|
||||
userId: ID!
|
||||
roleInGroup: GroupMemberRole!
|
||||
): User
|
||||
|
||||
RemoveUserFromGroup(
|
||||
groupId: ID!
|
||||
userId: ID!
|
||||
): User
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ type Query {
|
||||
|
||||
type Mutation {
|
||||
markAsRead(id: ID!): NOTIFIED
|
||||
markAllAsRead: [NOTIFIED]
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
|
||||
@ -82,6 +82,7 @@ input _PostFilter {
|
||||
emotions_single: _PostEMOTEDFilter
|
||||
emotions_every: _PostEMOTEDFilter
|
||||
group: _GroupFilter
|
||||
postsInMyGroups: Boolean
|
||||
}
|
||||
|
||||
enum _PostOrdering {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Given } from "cypress-cucumber-preprocessor/steps";
|
||||
import { gql } from '../../../backend/src/helpers/jest'
|
||||
|
||||
Given('somebody reported the following posts:', table => {
|
||||
table.hashes().forEach(({ submitterEmail, resourceId, reasonCategory, reasonDescription }) => {
|
||||
@ -10,7 +9,7 @@ Given('somebody reported the following posts:', table => {
|
||||
cy.factory()
|
||||
.build('user', {}, submitter)
|
||||
.authenticateAs(submitter)
|
||||
.mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
.mutate(`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
|
||||
reportId
|
||||
}
|
||||
|
||||
@ -15,11 +15,10 @@
|
||||
/* globals Cypress cy */
|
||||
import "cypress-file-upload";
|
||||
import { GraphQLClient, request } from 'graphql-request'
|
||||
import { gql } from '../../backend/src/helpers/jest'
|
||||
import config from '../../backend/src/config'
|
||||
|
||||
const authenticatedHeaders = (variables) => {
|
||||
const mutation = gql`
|
||||
const mutation = `
|
||||
mutation($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password)
|
||||
}
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
# Docker
|
||||
|
||||
## Apple M1 Platform
|
||||
|
||||
***Attention:** For using Docker commands in Apple M1 environments!*
|
||||
|
||||
```bash
|
||||
# set env variable for your shell
|
||||
$ export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
||||
```
|
||||
|
||||
For even more informations, see [Docker More Closely](#docker-more-closely)
|
||||
|
||||
### Docker Compose Override File For Apple M1 Platform
|
||||
|
||||
For Docker compose `up` or `build` commands, you can use our Apple M1 override file that specifies the M1 platform:
|
||||
|
||||
```bash
|
||||
# in main folder
|
||||
|
||||
# for production
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.apple-m1.override.yml up
|
||||
|
||||
# for production testing Docker images from DockerHub
|
||||
$ docker compose -f docker-compose.ocelotsocial-branded.yml -f docker-compose.apple-m1.override.yml up
|
||||
|
||||
# only once: init admin user and create indexes and contraints in Neo4j database
|
||||
$ docker compose exec backend /bin/sh -c "yarn prod:migrate init"
|
||||
```
|
||||
|
||||
## Docker More Closely In Main Code
|
||||
|
||||
To get more informations about the Apple M1 platform and to analyze the Docker builds etc. you find our documentation in our main code, [here](https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/DOCKER_MORE_CLOSELY.md).
|
||||
@ -1,36 +0,0 @@
|
||||
# This docker-compose file is just here for testing
|
||||
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
|
||||
########################################################
|
||||
# WEBAPP ###############################################
|
||||
########################################################
|
||||
webapp:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# BACKEND ##############################################
|
||||
########################################################
|
||||
backend:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# MAINTENANCE ##########################################
|
||||
########################################################
|
||||
maintenance:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# NEO4J ################################################
|
||||
########################################################
|
||||
neo4j:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# MAILSERVER TO FAKE SMTP ##############################
|
||||
########################################################
|
||||
# commented out, because otherwise override of production would error. and it seems unnecessary
|
||||
# mailserver:
|
||||
# platform: linux/amd64
|
||||
@ -7,6 +7,7 @@ services:
|
||||
dockerfile: ../webapp/Dockerfile
|
||||
context: ../webapp
|
||||
target: base
|
||||
command: sleep 0
|
||||
|
||||
webapp-code:
|
||||
image: ocelotsocialnetwork/webapp:local-code
|
||||
@ -14,10 +15,11 @@ services:
|
||||
dockerfile: ../webapp/Dockerfile
|
||||
context: ../webapp
|
||||
target: code
|
||||
command: sleep 0
|
||||
|
||||
webapp:
|
||||
image: ocelotsocialnetwork/webapp-branded:local-${CONFIGURATION}
|
||||
container_name: webapp
|
||||
container_name: webapp-branded
|
||||
build:
|
||||
dockerfile: src/docker/webapp.Dockerfile
|
||||
target: branded
|
||||
@ -51,6 +53,7 @@ services:
|
||||
dockerfile: ../backend/Dockerfile
|
||||
context: ../backend
|
||||
target: base
|
||||
command: sleep 0
|
||||
|
||||
backend-code:
|
||||
image: ocelotsocialnetwork/backend:local-code
|
||||
@ -58,13 +61,14 @@ services:
|
||||
dockerfile: ../backend/Dockerfile
|
||||
context: ../backend
|
||||
target: code
|
||||
command: sleep 0
|
||||
|
||||
backend:
|
||||
image: ocelotsocialnetwork/backend-branded:local-${CONFIGURATION}
|
||||
container_name: backend
|
||||
build:
|
||||
dockerfile: src/docker/backend.Dockerfile
|
||||
target: branded
|
||||
target: branded-branded
|
||||
context: .
|
||||
args:
|
||||
- CONFIGURATION=$CONFIGURATION
|
||||
@ -106,6 +110,7 @@ services:
|
||||
dockerfile: ../webapp/Dockerfile.maintenance
|
||||
context: ../webapp
|
||||
target: base
|
||||
command: sleep 0
|
||||
|
||||
maintenance-code:
|
||||
image: ocelotsocialnetwork/maintenance:local-code
|
||||
@ -113,11 +118,12 @@ services:
|
||||
dockerfile: ../webapp/Dockerfile.maintenance
|
||||
context: ../webapp
|
||||
target: code
|
||||
command: sleep 0
|
||||
|
||||
maintenance:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: ocelotsocialnetwork/maintenance-branded:local-${CONFIGURATION}
|
||||
container_name: maintenance
|
||||
container_name: maintenance-branded
|
||||
build:
|
||||
# TODO: Separate from webapp, this must be independent
|
||||
dockerfile: src/docker/maintenance.Dockerfile
|
||||
@ -152,7 +158,7 @@ services:
|
||||
|
||||
mailserver:
|
||||
image: djfarrelly/maildev
|
||||
container_name: mailserver
|
||||
container_name: mailserver-branded
|
||||
ports:
|
||||
- 1080:80
|
||||
networks:
|
||||
|
||||
47
deployment/scripts/cluster.backup.sh
Executable file
47
deployment/scripts/cluster.backup.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# base setup
|
||||
SCRIPT_PATH=$(realpath $0)
|
||||
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
|
||||
|
||||
# configuration
|
||||
CONFIGURATION=${CONFIGURATION:-"example"}
|
||||
KUBECONFIG=${KUBECONFIG:-${SCRIPT_DIR}/../configurations/${CONFIGURATION}/kubeconfig.yaml}
|
||||
BACKUP_DATE=$(date "+%F_%H-%M-%S")
|
||||
BACKUP_FOLDER=${BACKUP_FOLDER:-${SCRIPT_DIR}/../configurations/${CONFIGURATION}/backup/${BACKUP_DATE}}
|
||||
|
||||
# create backup fodler
|
||||
mkdir -p ${BACKUP_FOLDER}
|
||||
|
||||
# maintenance mode on
|
||||
${SCRIPT_DIR}/cluster.maintenance.sh on
|
||||
|
||||
# shutdown database
|
||||
kubectl --kubeconfig=${KUBECONFIG} get deployment ocelot-neo4j -o json \
|
||||
| jq '.spec.template.spec.containers[] += {"command": ["tail", "-f", "/dev/null"]}' \
|
||||
| kubectl --kubeconfig=${KUBECONFIG} apply -f -
|
||||
|
||||
# wait for the container to restart
|
||||
sleep 60
|
||||
|
||||
# database backup
|
||||
kubectl --kubeconfig=${KUBECONFIG} -n default exec -it \
|
||||
$(kubectl --kubeconfig=${KUBECONFIG} -n default get pods | grep ocelot-neo4j | awk '{ print $1 }') \
|
||||
-- neo4j-admin dump --to=/var/lib/neo4j/$BACKUP_DATE-neo4j-dump
|
||||
# copy neo4j backup to local drive
|
||||
kubectl --kubeconfig=${KUBECONFIG} cp \
|
||||
default/$(kubectl --kubeconfig=${KUBECONFIG} -n default get pods | grep ocelot-neo4j |awk '{ print $1 }'):/var/lib/neo4j/$BACKUP_DATE-neo4j-dump $BACKUP_FOLDER/neo4j-dump
|
||||
# copy image data
|
||||
kubectl --kubeconfig=${KUBECONFIG} cp \
|
||||
default/$(kubectl --kubeconfig=${KUBECONFIG} -n default get pods | grep ocelot-backend |awk '{ print $1 }'):/app/public/uploads $BACKUP_FOLDER/public-uploads
|
||||
|
||||
# restart database
|
||||
kubectl --kubeconfig=${KUBECONFIG} get deployment ocelot-neo4j -o json \
|
||||
| jq 'del(.spec.template.spec.containers[].command)' \
|
||||
| kubectl --kubeconfig=${KUBECONFIG} apply -f -
|
||||
|
||||
# wait for the container to restart
|
||||
sleep 60
|
||||
|
||||
# maintenance mode off
|
||||
${SCRIPT_DIR}/cluster.maintenance.sh off
|
||||
22
deployment/scripts/cluster.maintenance.sh
Executable file
22
deployment/scripts/cluster.maintenance.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# base setup
|
||||
SCRIPT_PATH=$(realpath $0)
|
||||
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
|
||||
|
||||
# configuration
|
||||
CONFIGURATION=${CONFIGURATION:-"example"}
|
||||
KUBECONFIG=${KUBECONFIG:-${SCRIPT_DIR}/../configurations/${CONFIGURATION}/kubeconfig.yaml}
|
||||
|
||||
case $1 in
|
||||
on)
|
||||
kubectl --kubeconfig=${KUBECONFIG} patch ingress ingress-ocelot-webapp --type merge --patch-file ${SCRIPT_DIR}/../src/kubernetes/patches/patch.ingress.maintenance.on.yaml
|
||||
;;
|
||||
off)
|
||||
kubectl --kubeconfig=${KUBECONFIG} patch ingress ingress-ocelot-webapp --type merge --patch-file ${SCRIPT_DIR}/../src/kubernetes/patches/patch.ingress.maintenance.off.yaml
|
||||
;;
|
||||
*)
|
||||
echo -e "Run this script with first argument either 'on' or 'off'"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
@ -14,7 +14,12 @@ ARG CONFIGURATION=example
|
||||
# copy public constants into the Docker image to brand it
|
||||
COPY configurations/${CONFIGURATION}/branding/static/ static/
|
||||
COPY configurations/${CONFIGURATION}/branding/constants/ constants/
|
||||
COPY configurations/${CONFIGURATION}/branding/locales/ locales/
|
||||
|
||||
# locales
|
||||
COPY configurations/${CONFIGURATION}/branding/locales/*.json locales/tmp/
|
||||
COPY src/tools/ tools/
|
||||
RUN apk add --no-cache bash jq
|
||||
RUN tools/merge-locales.sh
|
||||
|
||||
##################################################################################
|
||||
# BUILD ##########################################################################
|
||||
|
||||
@ -12,17 +12,16 @@ FROM $APP_IMAGE_CODE as code
|
||||
ARG CONFIGURATION=example
|
||||
|
||||
# copy public constants into the Docker image to brand it
|
||||
COPY src/tools/ tools/
|
||||
COPY configurations/${CONFIGURATION}/branding/static/ static/
|
||||
COPY configurations/${CONFIGURATION}/branding/constants/ constants/
|
||||
COPY configurations/${CONFIGURATION}/branding/locales/html/ locales/html/
|
||||
# COPY configurations/${CONFIGURATION}/branding/locales/index.js locales/index.js
|
||||
COPY configurations/${CONFIGURATION}/branding/locales/*.json locales/tmp/
|
||||
COPY configurations/${CONFIGURATION}/branding/assets/styles/imports/ assets/styles/imports/
|
||||
COPY configurations/${CONFIGURATION}/branding/assets/fonts/ assets/fonts/
|
||||
|
||||
# locales
|
||||
COPY configurations/${CONFIGURATION}/branding/locales/*.json locales/tmp/
|
||||
COPY src/tools/ tools/
|
||||
RUN apk add --no-cache bash jq
|
||||
|
||||
RUN tools/merge-locales.sh
|
||||
|
||||
##################################################################################
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
spec:
|
||||
rules:
|
||||
- host:
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: ocelot-webapp
|
||||
port:
|
||||
number: 3000
|
||||
@ -0,0 +1,12 @@
|
||||
spec:
|
||||
rules:
|
||||
- host:
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: ocelot-maintenance
|
||||
port:
|
||||
number: 80
|
||||
@ -1,34 +0,0 @@
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
|
||||
########################################################
|
||||
# WEBAPP ###############################################
|
||||
########################################################
|
||||
webapp:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# BACKEND ##############################################
|
||||
########################################################
|
||||
backend:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# MAINTENANCE ##########################################
|
||||
########################################################
|
||||
maintenance:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# NEO4J ################################################
|
||||
########################################################
|
||||
neo4j:
|
||||
platform: linux/amd64
|
||||
|
||||
########################################################
|
||||
# MAILSERVER TO FAKE SMTP ##############################
|
||||
########################################################
|
||||
# commented out, because otherwise override of production would error. and it seems unnecessary
|
||||
# mailserver:
|
||||
# platform: linux/amd64
|
||||
@ -55,7 +55,7 @@ Start Neo4J and confirm the database is running at [http://localhost:7474](http:
|
||||
|
||||
Here we describe some rarely used Cypher commands for Neo4j that are needed from time to time:
|
||||
|
||||
### Index And Contraint Commands
|
||||
### Index And Constraint Commands
|
||||
|
||||
If indexes or constraints are missing or not set correctly, the browser search will not work or the database seed for development will not work.
|
||||
|
||||
|
||||
@ -10,8 +10,8 @@ between different local Node versions:
|
||||
```bash
|
||||
# install Node
|
||||
$ cd webapp
|
||||
$ nvm install v16.19.0
|
||||
$ nvm use v16.19.0
|
||||
$ nvm install v19.4.0
|
||||
$ nvm use v19.4.0
|
||||
```
|
||||
|
||||
Install node dependencies with [yarn](https://yarnpkg.com/en/):
|
||||
|
||||
@ -375,4 +375,21 @@ $color-footer-link: $color-primary;
|
||||
* @tokens Locale Menu Color
|
||||
*/
|
||||
|
||||
$color-locale-menu: $text-color-soft;
|
||||
$color-locale-menu: $text-color-soft;
|
||||
|
||||
/**
|
||||
* @tokens Donation Bar Color
|
||||
*/
|
||||
|
||||
$color-donation-bar: $color-primary;
|
||||
$color-donation-bar-light: $color-primary-light;
|
||||
|
||||
/**
|
||||
* @tokens Toast Color
|
||||
*/
|
||||
|
||||
$color-toast-red: $color-danger;
|
||||
$color-toast-orange: $color-warning;
|
||||
$color-toast-yellow: $color-yellow;
|
||||
$color-toast-blue: $color-secondary;
|
||||
$color-toast-green: $color-success;
|
||||
|
||||
@ -11,22 +11,22 @@
|
||||
}
|
||||
|
||||
.iziToast.iziToast-color-red {
|
||||
background: $color-danger !important;
|
||||
border-color: $color-danger !important;
|
||||
background: $color-toast-red !important;
|
||||
border-color: $color-toast-red !important;
|
||||
}
|
||||
.iziToast.iziToast-color-orange {
|
||||
background: $color-warning !important;
|
||||
border-color: $color-warning !important;
|
||||
background: $color-toast-orange !important;
|
||||
border-color: $color-toast-orange !important;
|
||||
}
|
||||
.iziToast.iziToast-color-yellow {
|
||||
background: $color-yellow !important;
|
||||
border-color: $color-yellow !important;
|
||||
background: $color-toast-yellow !important;
|
||||
border-color: $color-toast-yellow !important;
|
||||
}
|
||||
.iziToast.iziToast-color-blue {
|
||||
background: $color-secondary !important;
|
||||
border-color: $color-secondary !important;
|
||||
background: $color-toast-blue !important;
|
||||
border-color: $color-toast-blue !important;
|
||||
}
|
||||
.iziToast.iziToast-color-green {
|
||||
background: $color-success !important;
|
||||
border-color: $color-success !important;
|
||||
background: $color-toast-green !important;
|
||||
border-color: $color-toast-green !important;
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Vuex from 'vuex'
|
||||
import AvatarMenu from './AvatarMenu.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
config.stubs['router-link'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'nuxt-link': true,
|
||||
'router-link': true,
|
||||
}
|
||||
|
||||
describe('AvatarMenu.vue', () => {
|
||||
let propsData, getters, wrapper, mocks
|
||||
@ -34,7 +36,7 @@ describe('AvatarMenu.vue', () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return mount(AvatarMenu, { propsData, localVue, store, mocks })
|
||||
return mount(AvatarMenu, { propsData, localVue, store, mocks, stubs })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
|
||||
@ -14,7 +14,7 @@ describe('Badges.vue', () => {
|
||||
}
|
||||
|
||||
it('has class "hc-badges"', () => {
|
||||
expect(Wrapper().contains('.hc-badges')).toBe(true)
|
||||
expect(Wrapper().find('.hc-badges').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('given a badge', () => {
|
||||
@ -23,7 +23,7 @@ describe('Badges.vue', () => {
|
||||
})
|
||||
|
||||
it('proxies badge icon, which is just a URL without metadata', () => {
|
||||
expect(Wrapper().contains('img[src="/api/path/to/some/icon"]')).toBe(true)
|
||||
expect(Wrapper().find('img[src="/api/path/to/some/icon"]').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import Category from './index'
|
||||
|
||||
@ -9,7 +9,7 @@ describe('Category', () => {
|
||||
let name
|
||||
|
||||
const Wrapper = () => {
|
||||
return shallowMount(Category, {
|
||||
return mount(Category, {
|
||||
localVue,
|
||||
propsData: {
|
||||
icon,
|
||||
@ -27,8 +27,9 @@ describe('Category', () => {
|
||||
it('shows Name', () => {
|
||||
expect(Wrapper().text()).toContain('Peter')
|
||||
})
|
||||
it('shows Icon Svg', () => {
|
||||
expect(Wrapper().contains('svg'))
|
||||
|
||||
it('shows base icon', () => {
|
||||
expect(Wrapper().find('.base-icon').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import CommentCard from './CommentCard.vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = global.localVue
|
||||
localVue.directive('scrollTo', jest.fn())
|
||||
|
||||
config.stubs['client-only'] = '<span><slot /></span>'
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
|
||||
describe('CommentCard.vue', () => {
|
||||
let propsData, mocks, stubs, getters, wrapper, Wrapper
|
||||
|
||||
@ -46,6 +43,8 @@ describe('CommentCard.vue', () => {
|
||||
}
|
||||
stubs = {
|
||||
ContentViewer: true,
|
||||
'client-only': true,
|
||||
'nuxt-link': true,
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
@ -56,7 +55,9 @@ describe('CommentCard.vue', () => {
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import CommentList from './CommentList'
|
||||
import Vuex from 'vuex'
|
||||
import Vue from 'vue'
|
||||
@ -8,10 +8,6 @@ const localVue = global.localVue
|
||||
localVue.filter('truncate', (string) => string)
|
||||
localVue.directive('scrollTo', jest.fn())
|
||||
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
config.stubs['client-only'] = '<span><slot /></span>'
|
||||
|
||||
describe('CommentList.vue', () => {
|
||||
let mocks, store, wrapper, propsData, stubs
|
||||
|
||||
@ -72,7 +68,10 @@ describe('CommentList.vue', () => {
|
||||
},
|
||||
}
|
||||
stubs = {
|
||||
EditorContent: "<div class='stub'></div>",
|
||||
EditorContent: true,
|
||||
'v-popover': true,
|
||||
'nuxt-link': true,
|
||||
'client-only': true,
|
||||
}
|
||||
})
|
||||
|
||||
@ -92,7 +91,9 @@ describe('CommentList.vue', () => {
|
||||
})
|
||||
|
||||
describe('scrollToAnchor mixin', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
describe('$route.hash !== `#comments`', () => {
|
||||
it('skips $scrollTo', () => {
|
||||
|
||||
@ -52,7 +52,7 @@ describe('ComponentSlider.vue', () => {
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.is('div')).toBe(true)
|
||||
expect(wrapper.element.tagName).toBe('DIV')
|
||||
})
|
||||
|
||||
it('click on next Button', async () => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Vuex from 'vuex'
|
||||
import VTooltip from 'v-tooltip'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
@ -10,7 +10,11 @@ localVue.use(Styleguide)
|
||||
localVue.use(VTooltip)
|
||||
localVue.use(Vuex)
|
||||
|
||||
config.stubs['router-link'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'router-link': {
|
||||
template: '<span><slot /></span>',
|
||||
},
|
||||
}
|
||||
|
||||
let getters, mutations, mocks, menuToggle, openModalSpy
|
||||
|
||||
@ -36,7 +40,7 @@ describe('ContentMenu.vue', () => {
|
||||
'auth/isAdmin': () => false,
|
||||
}
|
||||
|
||||
const openContentMenu = (values = {}) => {
|
||||
const openContentMenu = async (values = {}) => {
|
||||
const store = new Vuex.Store({ mutations, getters })
|
||||
const wrapper = mount(ContentMenu, {
|
||||
propsData: {
|
||||
@ -45,16 +49,17 @@ describe('ContentMenu.vue', () => {
|
||||
mocks,
|
||||
store,
|
||||
localVue,
|
||||
stubs,
|
||||
})
|
||||
menuToggle = wrapper.find('[data-test="content-menu-button"]')
|
||||
menuToggle.trigger('click')
|
||||
await menuToggle.trigger('click')
|
||||
return wrapper
|
||||
}
|
||||
|
||||
describe('owner of contribution', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = openContentMenu({
|
||||
beforeEach(async () => {
|
||||
wrapper = await openContentMenu({
|
||||
isOwner: true,
|
||||
resourceType: 'contribution',
|
||||
resource: {
|
||||
@ -86,9 +91,9 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
|
||||
describe('admin can', () => {
|
||||
it('pin unpinned post', () => {
|
||||
it('pin unpinned post', async () => {
|
||||
getters['auth/isAdmin'] = () => true
|
||||
const wrapper = openContentMenu({
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'contribution',
|
||||
resource: {
|
||||
@ -111,8 +116,8 @@ describe('ContentMenu.vue', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('unpin pinned post', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('unpin pinned post', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'contribution',
|
||||
resource: {
|
||||
@ -135,11 +140,11 @@ describe('ContentMenu.vue', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('can delete another user', () => {
|
||||
it('can delete another user', async () => {
|
||||
getters['auth/user'] = () => {
|
||||
return { id: 'some-user', slug: 'some-user' }
|
||||
}
|
||||
const wrapper = openContentMenu({
|
||||
const wrapper = await openContentMenu({
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
id: 'another-user',
|
||||
@ -161,8 +166,8 @@ describe('ContentMenu.vue', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('can not delete the own account', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can not delete the own account', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
id: 'some-user',
|
||||
@ -179,8 +184,8 @@ describe('ContentMenu.vue', () => {
|
||||
|
||||
describe('owner of comment can', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = openContentMenu({
|
||||
beforeEach(async () => {
|
||||
wrapper = await openContentMenu({
|
||||
isOwner: true,
|
||||
resourceType: 'comment',
|
||||
resource: {
|
||||
@ -208,10 +213,10 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
|
||||
describe('reporting', () => {
|
||||
it('a post of another user is possible', () => {
|
||||
it('a post of another user is possible', async () => {
|
||||
getters['auth/isAdmin'] = () => false
|
||||
getters['auth/isModerator'] = () => false
|
||||
const wrapper = openContentMenu({
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'contribution',
|
||||
resource: {
|
||||
@ -227,8 +232,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('report')
|
||||
})
|
||||
|
||||
it('a comment of another user is possible', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('a comment of another user is possible', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'comment',
|
||||
resource: {
|
||||
@ -244,8 +249,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('report')
|
||||
})
|
||||
|
||||
it('another user is possible', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('another user is possible', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
@ -261,8 +266,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('report')
|
||||
})
|
||||
|
||||
it('another organization is possible', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('another organization is possible', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'organization',
|
||||
resource: {
|
||||
@ -280,10 +285,10 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
|
||||
describe('moderator', () => {
|
||||
it('can disable posts', () => {
|
||||
it('can disable posts', async () => {
|
||||
getters['auth/isAdmin'] = () => false
|
||||
getters['auth/isModerator'] = () => true
|
||||
const wrapper = openContentMenu({
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'contribution',
|
||||
resource: {
|
||||
@ -300,8 +305,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('disable')
|
||||
})
|
||||
|
||||
it('can disable comments', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can disable comments', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'comment',
|
||||
resource: {
|
||||
@ -318,8 +323,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('disable')
|
||||
})
|
||||
|
||||
it('can disable users', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can disable users', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
@ -336,8 +341,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('disable')
|
||||
})
|
||||
|
||||
it('can disable organizations', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can disable organizations', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'organization',
|
||||
resource: {
|
||||
@ -354,8 +359,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('disable')
|
||||
})
|
||||
|
||||
it('can release posts', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can release posts', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'contribution',
|
||||
resource: {
|
||||
@ -372,8 +377,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release')
|
||||
})
|
||||
|
||||
it('can release comments', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can release comments', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'comment',
|
||||
resource: {
|
||||
@ -390,8 +395,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release')
|
||||
})
|
||||
|
||||
it('can release users', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can release users', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
@ -408,8 +413,8 @@ describe('ContentMenu.vue', () => {
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release')
|
||||
})
|
||||
|
||||
it('can release organizations', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can release organizations', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'organization',
|
||||
resource: {
|
||||
@ -428,10 +433,10 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
|
||||
describe('user', () => {
|
||||
it('can access settings', () => {
|
||||
it('can access settings', async () => {
|
||||
getters['auth/isAdmin'] = () => false
|
||||
getters['auth/isModerator'] = () => false
|
||||
const wrapper = openContentMenu({
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: true,
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
@ -448,8 +453,8 @@ describe('ContentMenu.vue', () => {
|
||||
).toBe('/settings')
|
||||
})
|
||||
|
||||
it('can mute other users', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can mute other users', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
@ -472,8 +477,8 @@ describe('ContentMenu.vue', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('can unmute muted users', () => {
|
||||
const wrapper = openContentMenu({
|
||||
it('can unmute muted users', async () => {
|
||||
const wrapper = await openContentMenu({
|
||||
isOwner: false,
|
||||
resourceType: 'user',
|
||||
resource: {
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import GroupContentMenu from './GroupContentMenu.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['router-link'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'router-link': {
|
||||
template: '<span><slot /></span>',
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
usage: 'groupTeaser',
|
||||
@ -24,7 +28,7 @@ describe('GroupContentMenu', () => {
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(GroupContentMenu, { propsData, mocks, localVue })
|
||||
return mount(GroupContentMenu, { propsData, mocks, localVue, stubs })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionForm from './ContributionForm.vue'
|
||||
|
||||
import Vuex from 'vuex'
|
||||
@ -11,9 +11,11 @@ global.MutationObserver = MutationObserver
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['client-only'] = '<span><slot /></span>'
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'client-only': true,
|
||||
'nuxt-link': true,
|
||||
'v-popover': true,
|
||||
}
|
||||
|
||||
describe('ContributionForm.vue', () => {
|
||||
let wrapper, postTitleInput, expectedParams, cancelBtn, mocks, propsData
|
||||
@ -88,6 +90,7 @@ describe('ContributionForm.vue', () => {
|
||||
localVue,
|
||||
store,
|
||||
propsData,
|
||||
stubs,
|
||||
})
|
||||
}
|
||||
|
||||
@ -163,7 +166,7 @@ describe('ContributionForm.vue', () => {
|
||||
.mockImplementation(function () {
|
||||
this.onload({ target: { result: 'someUrlToImage' } })
|
||||
})
|
||||
wrapper.find(ImageUploader).vm.$emit('addHeroImage', imageUpload)
|
||||
wrapper.findComponent(ImageUploader).vm.$emit('addHeroImage', imageUpload)
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
expect(spy).toHaveBeenCalledWith(imageUpload)
|
||||
|
||||
@ -115,9 +115,9 @@ describe('DeleteData.vue', () => {
|
||||
enableContributionDeletionCheckbox = wrapper.find(
|
||||
'[data-test="contributions-deletion-checkbox"]',
|
||||
)
|
||||
enableContributionDeletionCheckbox.trigger('click')
|
||||
enableContributionDeletionCheckbox.setChecked(true)
|
||||
enableCommentDeletionCheckbox = wrapper.find('[data-test="comments-deletion-checkbox"]')
|
||||
enableCommentDeletionCheckbox.trigger('click')
|
||||
enableCommentDeletionCheckbox.setChecked(true)
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@ -133,7 +133,7 @@ describe('DeleteData.vue', () => {
|
||||
enableContributionDeletionCheckbox = wrapper.find(
|
||||
'[data-test="contributions-deletion-checkbox"]',
|
||||
)
|
||||
enableContributionDeletionCheckbox.trigger('click')
|
||||
enableContributionDeletionCheckbox.setChecked(true)
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@ -147,7 +147,7 @@ describe('DeleteData.vue', () => {
|
||||
|
||||
it("deletes a user's comments if requested", () => {
|
||||
enableCommentDeletionCheckbox = wrapper.find('[data-test="comments-deletion-checkbox"]')
|
||||
enableCommentDeletionCheckbox.trigger('click')
|
||||
enableCommentDeletionCheckbox.setChecked(true)
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
@ -35,7 +35,7 @@ describe('Editor.vue', () => {
|
||||
|
||||
describe('mount', () => {
|
||||
it('renders', () => {
|
||||
expect(Wrapper().is('div')).toBe(true)
|
||||
expect(Wrapper().element.tagName).toBe('DIV')
|
||||
})
|
||||
|
||||
describe('given a piece of text', () => {
|
||||
|
||||
@ -42,7 +42,7 @@ describe('Embed.vue', () => {
|
||||
propsData.node = { attrs: { href: 'https://www.youtube.com/watch?v=qkdXAtO40Fo' } }
|
||||
const wrapper = Wrapper({ propsData })
|
||||
await wrapper.html()
|
||||
expect(wrapper.contains('embed-component-stub')).toBe(true)
|
||||
expect(wrapper.find('embed-component-stub').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -160,7 +160,7 @@ describe('EmbedComponent.vue', () => {
|
||||
|
||||
describe('sets permanently', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input[type=checkbox]').trigger('click')
|
||||
wrapper.find('input[type=checkbox]').setChecked(true)
|
||||
wrapper.find('[data-test="play-now-button"]').trigger('click')
|
||||
})
|
||||
|
||||
|
||||
@ -19,7 +19,9 @@ describe('EnterNonce ', () => {
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
Wrapper = () => {
|
||||
return mount(EnterNonce, {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<a href="#" slot="default" slot-scope="{ toggleMenu }" @click.prevent="toggleMenu()">
|
||||
<ds-text bold size="large">{{ $t('admin.categories.name') }}</ds-text>
|
||||
</a>
|
||||
<template slot="popover">
|
||||
<template #popover>
|
||||
<div class="category-menu-options">
|
||||
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
|
||||
<categories-filter v-if="categoriesActive" />
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
>
|
||||
<base-icon class="dropdown-arrow" name="angle-down" />
|
||||
</base-button>
|
||||
<template slot="popover">
|
||||
<template #popover>
|
||||
<filter-menu-component />
|
||||
</template>
|
||||
</dropdown>
|
||||
|
||||
@ -9,12 +9,14 @@ let wrapper
|
||||
describe('FollowingFilter', () => {
|
||||
const mutations = {
|
||||
'posts/TOGGLE_FILTER_BY_FOLLOWED': jest.fn(),
|
||||
'posts/TOGGLE_FILTER_BY_MY_GROUPS': jest.fn(),
|
||||
}
|
||||
const getters = {
|
||||
'auth/user': () => {
|
||||
return { id: 'u34' }
|
||||
},
|
||||
'posts/filteredByUsersFollowed': jest.fn(),
|
||||
'posts/filteredByPostsInMyGroups': jest.fn(),
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
@ -34,12 +36,18 @@ describe('FollowingFilter', () => {
|
||||
describe('mount', () => {
|
||||
it('sets "filter-by-followed" button attribute `filled`', () => {
|
||||
getters['posts/filteredByUsersFollowed'] = jest.fn(() => true)
|
||||
getters['posts/filteredByPostsInMyGroups'] = jest.fn(() => true)
|
||||
const wrapper = Wrapper()
|
||||
expect(
|
||||
wrapper
|
||||
.find('.following-filter .filter-list .follower-item .base-button')
|
||||
.classes('--filled'),
|
||||
).toBe(true)
|
||||
expect(
|
||||
wrapper
|
||||
.find('.following-filter .filter-list .posts-in-my-groups-item .base-button')
|
||||
.classes('--filled'),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
describe('click "filter-by-followed" button', () => {
|
||||
@ -48,5 +56,14 @@ describe('FollowingFilter', () => {
|
||||
expect(mutations['posts/TOGGLE_FILTER_BY_FOLLOWED']).toHaveBeenCalledWith({}, 'u34')
|
||||
})
|
||||
})
|
||||
|
||||
describe('click "filter-by-my-groups" button', () => {
|
||||
it('calls TOGGLE_FILTER_BY_MY_GROUPS', () => {
|
||||
wrapper
|
||||
.find('.following-filter .filter-list .posts-in-my-groups-item .base-button')
|
||||
.trigger('click')
|
||||
expect(mutations['posts/TOGGLE_FILTER_BY_MY_GROUPS']).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -10,6 +10,15 @@
|
||||
@click="toggleFilteredByFollowed(currentUser.id)"
|
||||
/>
|
||||
</li>
|
||||
<li class="item posts-in-my-groups-item">
|
||||
<labeled-button
|
||||
icon="users"
|
||||
:label="$t('filter-menu.my-groups')"
|
||||
:filled="filteredByPostsInMyGroups"
|
||||
:title="$t('contribution.filterMyGroups')"
|
||||
@click="toggleFilteredByMyGroups()"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
</filter-menu-section>
|
||||
</template>
|
||||
@ -28,12 +37,14 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
filteredByUsersFollowed: 'posts/filteredByUsersFollowed',
|
||||
filteredByPostsInMyGroups: 'posts/filteredByPostsInMyGroups',
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
toggleFilteredByFollowed: 'posts/TOGGLE_FILTER_BY_FOLLOWED',
|
||||
toggleFilteredByMyGroups: 'posts/TOGGLE_FILTER_BY_MY_GROUPS',
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import GroupForm from './GroupForm.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'nuxt-link': true,
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
update: false,
|
||||
@ -25,7 +27,7 @@ describe('GroupForm', () => {
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(GroupForm, { propsData, mocks, localVue })
|
||||
return mount(GroupForm, { propsData, mocks, localVue, stubs })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -1,26 +1,65 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import GroupMember from './GroupMember.vue'
|
||||
import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const propsData = {
|
||||
groupId: '',
|
||||
groupMembers: [],
|
||||
groupId: 'group-id',
|
||||
groupMembers: [
|
||||
{
|
||||
slug: 'owner',
|
||||
id: 'owner',
|
||||
myRoleInGroup: 'owner',
|
||||
},
|
||||
{
|
||||
slug: 'user',
|
||||
id: 'user',
|
||||
myRoleInGroup: 'usual',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
'nuxt-link': true,
|
||||
}
|
||||
|
||||
const apolloMock = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce({ message: 'Oh no!' })
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
ChangeGroupMemberRole: {
|
||||
slug: 'user',
|
||||
id: 'user',
|
||||
myRoleInGroup: 'admin',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
|
||||
describe('GroupMember', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$t: jest.fn((t) => t),
|
||||
$apollo: {
|
||||
mutate: apolloMock,
|
||||
},
|
||||
$toast: {
|
||||
error: toastErrorMock,
|
||||
success: toastSuccessMock,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(GroupMember, { propsData, mocks, localVue })
|
||||
return mount(GroupMember, { propsData, mocks, localVue, stubs })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -30,5 +69,120 @@ describe('GroupMember', () => {
|
||||
it('renders', () => {
|
||||
expect(wrapper.findAll('.group-member')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('has two users in table', () => {
|
||||
expect(wrapper.find('tbody').findAll('tr')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('has no modal', () => {
|
||||
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('change user role', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper
|
||||
.find('tbody')
|
||||
.findAll('tr')
|
||||
.at(1)
|
||||
.find('select')
|
||||
.findAll('option')
|
||||
.at(2)
|
||||
.setSelected()
|
||||
wrapper.find('tbody').findAll('tr').at(1).find('select').trigger('change')
|
||||
})
|
||||
|
||||
describe('with server error', () => {
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with server success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMock).toBeCalledWith({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: { groupId: 'group-id', userId: 'user', roleInGroup: 'admin' },
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('group.changeMemberRole')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('click remove user', () => {
|
||||
beforeAll(() => {
|
||||
apolloMock.mockRejectedValueOnce({ message: 'Oh no!!' }).mockResolvedValue({
|
||||
data: {
|
||||
RemoveUserFromGroup: {
|
||||
slug: 'user',
|
||||
id: 'user',
|
||||
myRoleInGroup: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('tbody').findAll('tr').at(1).find('button').trigger('click')
|
||||
})
|
||||
|
||||
it('opens the modal', () => {
|
||||
expect(wrapper.find('div.ds-modal-wrapper').isVisible()).toBe(true)
|
||||
})
|
||||
|
||||
describe('click on cancel', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('div.ds-modal-wrapper').find('button.ds-button-ghost').trigger('click')
|
||||
})
|
||||
|
||||
it('closes the modal', () => {
|
||||
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('click on confirm with server error', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('div.ds-modal-wrapper').find('button.ds-button-primary').trigger('click')
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Oh no!!')
|
||||
})
|
||||
|
||||
it('closes the modal', () => {
|
||||
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('click on confirm with success', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper.find('div.ds-modal-wrapper').find('button.ds-button-primary').trigger('click')
|
||||
})
|
||||
|
||||
it('calls the API', () => {
|
||||
expect(apolloMock).toBeCalledWith({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: { groupId: 'group-id', userId: 'user' },
|
||||
})
|
||||
})
|
||||
|
||||
it('emits load group members', () => {
|
||||
expect(wrapper.emitted('loadGroupMembers')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('group.memberRemoved')
|
||||
})
|
||||
|
||||
it('closes the modal', () => {
|
||||
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -53,30 +53,33 @@
|
||||
</ds-chip>
|
||||
</template>
|
||||
<template #edit="scope">
|
||||
<ds-button v-if="scope.row.myRoleInGroup !== 'owner'" size="small" primary disabled>
|
||||
<!-- TODO: implement removal of group members -->
|
||||
<!-- :disabled="scope.row.myRoleInGroup === 'owner'"
|
||||
-->
|
||||
<base-button
|
||||
v-if="scope.row.myRoleInGroup !== 'owner'"
|
||||
size="small"
|
||||
primary
|
||||
@click="
|
||||
isOpen = true
|
||||
userId = scope.row.id
|
||||
"
|
||||
>
|
||||
{{ $t('group.removeMemberButton') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
</ds-table>
|
||||
<!-- TODO: implement removal of group members -->
|
||||
<!-- TODO: change to ocelot.social modal -->
|
||||
<!-- <ds-modal
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
:title="`${$t('group.removeMember')}`"
|
||||
force
|
||||
extended
|
||||
:confirm-label="$t('group.removeMember')"
|
||||
:cancel-label="$t('actions.cancel')"
|
||||
@confirm="deleteMember(memberId)"
|
||||
/> -->
|
||||
<ds-modal
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
:title="`${$t('group.removeMember')}`"
|
||||
force
|
||||
extended
|
||||
:confirm-label="$t('group.removeMember')"
|
||||
:cancel-label="$t('actions.cancel')"
|
||||
@confirm="removeUser()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { changeGroupMemberRoleMutation } from '~/graphql/groups.js'
|
||||
import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js'
|
||||
|
||||
export default {
|
||||
name: 'GroupMember',
|
||||
@ -96,6 +99,8 @@ export default {
|
||||
query: '',
|
||||
searchProcess: null,
|
||||
user: {},
|
||||
isOpen: false,
|
||||
userId: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -139,6 +144,25 @@ export default {
|
||||
this.$toast.error(error.message)
|
||||
}
|
||||
},
|
||||
removeUser() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
variables: { groupId: this.groupId, userId: this.userId },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.$emit('loadGroupMembers')
|
||||
this.$toast.success(
|
||||
this.$t('group.memberRemoved', { name: data.RemoveUserFromGroup.slug }),
|
||||
)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toast.error(error.message)
|
||||
})
|
||||
.finally(() => {
|
||||
this.userId = null
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { config, shallowMount } from '@vue/test-utils'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
|
||||
import Hashtag from './Hashtag'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'nuxt-link': true,
|
||||
}
|
||||
|
||||
describe('Hashtag', () => {
|
||||
let id
|
||||
@ -15,6 +17,7 @@ describe('Hashtag', () => {
|
||||
propsData: {
|
||||
id,
|
||||
},
|
||||
stubs,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ describe('HashtagsFilter.vue', () => {
|
||||
|
||||
it('renders a card', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.is('.base-card')).toBe(true)
|
||||
expect(wrapper.classes('base-card')).toBe(true)
|
||||
})
|
||||
|
||||
describe('click clear search button', () => {
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import InviteButton from './InviteButton.vue'
|
||||
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'v-popover': {
|
||||
template: '<span><slot /></span>',
|
||||
},
|
||||
}
|
||||
|
||||
describe('InviteButton.vue', () => {
|
||||
let wrapper
|
||||
@ -22,7 +26,7 @@ describe('InviteButton.vue', () => {
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(InviteButton, { mocks, propsData })
|
||||
return mount(InviteButton, { mocks, propsData, stubs })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -30,12 +34,12 @@ describe('InviteButton.vue', () => {
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.contains('.invite-button')).toBe(true)
|
||||
expect(wrapper.find('.invite-button').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('open popup', () => {
|
||||
wrapper.find('.base-button').trigger('click')
|
||||
expect(wrapper.contains('.invite-button')).toBe(true)
|
||||
expect(wrapper.find('.invite-button').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('invite codes not available', async () => {
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<dropdown class="invite-button" offset="8" :placement="placement">
|
||||
<template #default="{ toggleMenu }">
|
||||
<base-button icon="user-plus" circle ghost @click.prevent="toggleMenu" />
|
||||
<base-button
|
||||
icon="user-plus"
|
||||
circle
|
||||
ghost
|
||||
v-tooltip="{
|
||||
content: $t('invite-codes.button.tooltip'),
|
||||
placement: 'bottom-start',
|
||||
}"
|
||||
@click.prevent="toggleMenu"
|
||||
/>
|
||||
</template>
|
||||
<template #popover>
|
||||
<div class="invite-button-menu-popover">
|
||||
@ -15,10 +24,7 @@
|
||||
ghost
|
||||
@click="copyInviteLink"
|
||||
>
|
||||
<ds-text bold>
|
||||
{{ $t('invite-codes.copy-code') }}
|
||||
{{ inviteCode.code }}
|
||||
</ds-text>
|
||||
<ds-text bold>{{ $t('invite-codes.copy-code') }}</ds-text>
|
||||
</base-button>
|
||||
</base-card>
|
||||
</div>
|
||||
@ -108,6 +114,6 @@ export default {
|
||||
}
|
||||
|
||||
.invite-code {
|
||||
left: 50%;
|
||||
margin-left: 25%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import LocaleSwitch from './LocaleSwitch.vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['client-only'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'client-only': true,
|
||||
}
|
||||
|
||||
describe('LocaleSwitch.vue', () => {
|
||||
let wrapper, mocks, computed, deutschLanguageItem, getters
|
||||
@ -65,7 +67,7 @@ describe('LocaleSwitch.vue', () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return mount(LocaleSwitch, { mocks, localVue, computed, store })
|
||||
return mount(LocaleSwitch, { mocks, localVue, computed, store, stubs })
|
||||
}
|
||||
|
||||
describe('with current user', () => {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import LoginButton from './LoginButton.vue'
|
||||
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'v-popover': true,
|
||||
'nuxt-link': true,
|
||||
}
|
||||
|
||||
describe('LoginButton.vue', () => {
|
||||
let wrapper
|
||||
@ -22,7 +25,7 @@ describe('LoginButton.vue', () => {
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(LoginButton, { mocks, propsData })
|
||||
return mount(LoginButton, { mocks, propsData, stubs })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -30,12 +33,12 @@ describe('LoginButton.vue', () => {
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.contains('.login-button')).toBe(true)
|
||||
expect(wrapper.find('.login-button').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('open popup', () => {
|
||||
wrapper.find('.base-button').trigger('click')
|
||||
expect(wrapper.contains('.login-button')).toBe(true)
|
||||
expect(wrapper.find('.login-button').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,15 +2,17 @@ import Vue from 'vue'
|
||||
import LoginForm from './LoginForm.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
config.stubs['locale-switch'] = '<span><slot /></span>'
|
||||
config.stubs['client-only'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'nuxt-link': true,
|
||||
'locale-switch': true,
|
||||
'client-only': true,
|
||||
}
|
||||
|
||||
const authUserMock = jest.fn().mockReturnValue({ activeCategories: [] })
|
||||
|
||||
@ -46,7 +48,7 @@ describe('LoginForm', () => {
|
||||
error: jest.fn(),
|
||||
},
|
||||
}
|
||||
return mount(LoginForm, { mocks, localVue, propsData, store })
|
||||
return mount(LoginForm, { mocks, localVue, propsData, store, stubs })
|
||||
}
|
||||
|
||||
describe('fill in email and password and submit', () => {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import MasonryGridItem from './MasonryGridItem'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['ds-grid-item'] = '<span><slot /></span>'
|
||||
const stubs = {
|
||||
'ds-grid-item': true,
|
||||
}
|
||||
|
||||
describe('MasonryGridItem', () => {
|
||||
let wrapper
|
||||
@ -11,34 +13,34 @@ describe('MasonryGridItem', () => {
|
||||
describe('given an imageAspectRatio', () => {
|
||||
it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => {
|
||||
const propsData = { imageAspectRatio: 2 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(13)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 15 when the ratio is between 1.3 and 1', () => {
|
||||
const propsData = { imageAspectRatio: 1.1 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(15)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => {
|
||||
const propsData = { imageAspectRatio: 0.7 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(18)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 25 when the ratio is lower than 0.7', () => {
|
||||
const propsData = { imageAspectRatio: 0.3 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs })
|
||||
expect(wrapper.vm.rowSpan).toBe(25)
|
||||
})
|
||||
|
||||
describe('given no aspect ratio', () => {
|
||||
it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => {
|
||||
wrapper = mount(MasonryGridItem, { localVue })
|
||||
wrapper = mount(MasonryGridItem, { localVue, stubs })
|
||||
expect(wrapper.vm.rowSpan).toBe(8)
|
||||
})
|
||||
})
|
||||
|
||||
@ -57,9 +57,9 @@ describe('Modal.vue', () => {
|
||||
|
||||
it('initially empty', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.contains(ConfirmModal)).toBe(false)
|
||||
expect(wrapper.contains(DisableModal)).toBe(false)
|
||||
expect(wrapper.contains(ReportModal)).toBe(false)
|
||||
expect(wrapper.findComponent(ConfirmModal).exists()).toBe(false)
|
||||
expect(wrapper.findComponent(DisableModal).exists()).toBe(false)
|
||||
expect(wrapper.findComponent(ReportModal).exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('store/modal holds data to disable', () => {
|
||||
@ -78,11 +78,11 @@ describe('Modal.vue', () => {
|
||||
})
|
||||
|
||||
it('renders disable modal', () => {
|
||||
expect(wrapper.contains(DisableModal)).toBe(true)
|
||||
expect(wrapper.findComponent(DisableModal).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('passes data to disable modal', () => {
|
||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||
expect(wrapper.findComponent(DisableModal).props()).toEqual({
|
||||
type: 'contribution',
|
||||
name: 'some title',
|
||||
id: 'c456',
|
||||
@ -91,9 +91,9 @@ describe('Modal.vue', () => {
|
||||
|
||||
describe('child component emits close', () => {
|
||||
it('turns empty', async () => {
|
||||
wrapper.find(DisableModal).vm.$emit('close')
|
||||
wrapper.findComponent(DisableModal).vm.$emit('close')
|
||||
await Vue.nextTick()
|
||||
expect(wrapper.contains(DisableModal)).toBe(false)
|
||||
expect(wrapper.findComponent(DisableModal).exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@ -109,7 +109,7 @@ describe('Modal.vue', () => {
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||
expect(wrapper.findComponent(DisableModal).props()).toEqual({
|
||||
type: 'comment',
|
||||
name: 'Author name',
|
||||
id: 'c456',
|
||||
@ -124,7 +124,7 @@ describe('Modal.vue', () => {
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||
expect(wrapper.findComponent(DisableModal).props()).toEqual({
|
||||
type: 'comment',
|
||||
name: '',
|
||||
id: 'c456',
|
||||
@ -142,7 +142,7 @@ describe('Modal.vue', () => {
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||
expect(wrapper.findComponent(DisableModal).props()).toEqual({
|
||||
type: 'user',
|
||||
name: 'Username',
|
||||
id: 'u456',
|
||||
@ -160,7 +160,7 @@ describe('Modal.vue', () => {
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||
expect(wrapper.findComponent(DisableModal).props()).toEqual({
|
||||
type: 'something',
|
||||
name: null,
|
||||
id: 's456',
|
||||
|
||||
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