diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 1d2aef77e..595c9d584 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,25 +1,9 @@
---
-name: 🐛 Bug report
-about: Create a report to help us improve
+name: 🐛 Bug Report
+about: Create a report to help us to improve.
labels: bug
title: 🐛 [Bug]
---
-## :bug: Bugreport
+## :bug: Bug Report
-
-### Steps to reproduce the behavior
-1.
-2.
-3.
-4. ...
-5. Profit
-
-### Expected behavior
-
-
-### Version & Environment
-
-
-### Additional context
-
diff --git a/.github/ISSUE_TEMPLATE/devops_ticket.md b/.github/ISSUE_TEMPLATE/devops_ticket.md
index 77355d1cf..115664911 100644
--- a/.github/ISSUE_TEMPLATE/devops_ticket.md
+++ b/.github/ISSUE_TEMPLATE/devops_ticket.md
@@ -1,24 +1,9 @@
---
-name: 💥 DevOps ticket
-about: Help us manage our deployed App.
+name: 💥 DevOps Ticket
+about: Help us manage our deployed app.
labels: devops
title: 💥 [DevOps]
---
-## 💥 DevOps ticket
+## 💥 DevOps Ticket
-
-### Motive
-
-
-### Related issues
-
-
-### Implementation
-
-
-### Validation
-
-
-### Additional context
-
diff --git a/.github/ISSUE_TEMPLATE/epic.md b/.github/ISSUE_TEMPLATE/epic.md
new file mode 100644
index 000000000..cf72cd673
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/epic.md
@@ -0,0 +1,12 @@
+---
+name: 🌟 Epic
+about: Define a big development step.
+labels: epic
+title: 🌟 [EPIC]
+---
+
+
+
+## 🌟 EPIC
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index ef3b30be2..beae80901 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,24 +1,9 @@
---
-name: 🚀 Feature request
-about: Suggest an idea for this project
+name: 🚀 Feature Request
+about: Suggest an idea for this project.
labels: feature
title: 🚀 [Feature]
---
-## :rocket: Feature
+## :rocket: Feature Request
-
-### User Problem
-
-
-### Implementation
-
-
-### Design & Layout
-
-
-### Validation
-
-
-### Additional context
-
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index aabbc0f0a..40e6e381b 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -1,12 +1,12 @@
---
name: 💬 Question
-about: If you need help understanding HumanConnection.
+about: If you need help understanding ocelot.social.
labels: question
title: 💬 [Question]
---
-
-
+
+
-## :speech_balloon: Question
+## 💬 Question
diff --git a/.github/ISSUE_TEMPLATE/refactor_tickets.md b/.github/ISSUE_TEMPLATE/refactor_tickets.md
index b595abd5d..d1841e35e 100644
--- a/.github/ISSUE_TEMPLATE/refactor_tickets.md
+++ b/.github/ISSUE_TEMPLATE/refactor_tickets.md
@@ -1,21 +1,10 @@
---
-name: 🔧 Refactor ticket
+name: 🔧 Refactor
about: Help us improve our code by refactoring it.
labels: refactor
title: 🔧 [Refactor]
---
-## :zap: Refactor ticket
+## 🔧 Refactor
-### Motive
-
-
-### Related issues
-
-
-### Implementation
-
-
-### Additional context
-
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..c5433c921
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,179 @@
+version: 2
+updates:
+- package-ecosystem: npm
+ directory: "/"
+ 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
+- package-ecosystem: npm
+ directory: "/backend"
+ 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
+- package-ecosystem: npm
+ directory: "/webapp"
+ 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
+- package-ecosystem: docker
+ directory: "/backend"
+ 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
+- package-ecosystem: docker
+ directory: "/neo4j"
+ schedule:
+ interval: daily
+ time: "04:00"
+ open-pull-requests-limit: 10
+ ignore:
+ - dependency-name: neo4j
+ versions:
+ - 4.2.3
+ - 4.2.4
+- package-ecosystem: docker
+ directory: "/deployment/legacy-migration/maintenance-worker"
+ schedule:
+ interval: daily
+ time: "04:00"
+ open-pull-requests-limit: 10
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 24350a81a..1c7927665 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- master
+ # - 4451-new-deployment-with-base-and-code # for testing while developing
jobs:
##############################################################################
@@ -55,9 +56,9 @@ jobs:
# NEO4J ##################################################################
##########################################################################
- name: Neo4J | Build `community` image
- run: |
- docker build --target community -t "ocelotsocialnetwork/neo4j:latest" -t "ocelotsocialnetwork/neo4j:community" -t "ocelotsocialnetwork/neo4j:${VERSION}" -t "ocelotsocialnetwork/neo4j:${BUILD_VERSION}" neo4j/
- docker save "ocelotsocialnetwork/neo4j" > /tmp/neo4j.tar
+ run: docker build --target community -t "ocelotsocialnetwork/neo4j:latest" -t "ocelotsocialnetwork/neo4j:community" -t "ocelotsocialnetwork/neo4j:${VERSION}" -t "ocelotsocialnetwork/neo4j:${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" > /tmp/neo4j.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
@@ -91,10 +92,13 @@ jobs:
##########################################################################
# BUILD BACKEND DOCKER IMAGE (production) ################################
##########################################################################
- - name: backend | Build `production` image
+ - name: Backend | Build `production` image
run: |
- docker build --target production -t "ocelotsocialnetwork/backend:latest" -t "ocelotsocialnetwork/backend:${VERSION}" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}" backend/
- docker save "ocelotsocialnetwork/backend" > /tmp/backend.tar
+ 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/
+ - name: Backend | Save docker image
+ run: docker save "ocelotsocialnetwork/backend" > /tmp/backend.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
@@ -128,10 +132,13 @@ jobs:
##########################################################################
# BUILD WEBAPP DOCKER IMAGE (build) ######################################
##########################################################################
- - name: webapp | Build `production` image
+ - name: Webapp | Build `production` image
run: |
- docker build --target production -t "ocelotsocialnetwork/webapp:latest" -t "ocelotsocialnetwork/webapp:${VERSION}" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}" webapp/
- docker save "ocelotsocialnetwork/webapp" > /tmp/webapp.tar
+ 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/
+ - name: Webapp | Save docker image
+ run: docker save "ocelotsocialnetwork/webapp" > /tmp/webapp.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
@@ -165,11 +172,13 @@ jobs:
##########################################################################
# BUILD MAINTENANCE DOCKER IMAGE (build) #################################
##########################################################################
- - name: maintenance | Build `production` image
- # TODO: --target production
+ - name: Maintenance | Build `production` image
run: |
- docker build -t "ocelotsocialnetwork/maintenance:latest" -t "ocelotsocialnetwork/maintenance:${VERSION}" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" webapp/ -f webapp/Dockerfile.maintenance
- docker save "ocelotsocialnetwork/maintenance" > /tmp/maintenance.tar
+ 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
+ - name: Maintenance | Save docker image
+ run: docker save "ocelotsocialnetwork/maintenance" > /tmp/maintenance.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 276589dc0..2bec6cebb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -194,7 +194,6 @@ jobs:
##########################################################################
# UNIT TESTS BACKEND #####################################################
##########################################################################
- # TODO: Why do we need those .envs?
- name: backend | copy env files webapp
run: cp webapp/.env.template webapp/.env
- name: backend | copy env files backend
@@ -205,6 +204,17 @@ jobs:
run: docker-compose exec -T backend yarn db:migrate init
- name: backend | Unit test
run: docker-compose exec -T backend yarn test
+ ##########################################################################
+ # COVERAGE CHECK BACKEND #################################################
+ ##########################################################################
+ - name: backend | Coverage check
+ uses: webcraftmedia/coverage-check-action@master
+ with:
+ report_name: Coverage Backend
+ type: lcov
+ result_path: ./coverage/lcov.info
+ min_coverage: 58
+ token: ${{ github.token }}
##############################################################################
# JOB: UNIT TEST WEBAPP ######################################################
@@ -230,9 +240,8 @@ jobs:
- name: Load Docker Image
run: docker load < /tmp/webapp.tar
##########################################################################
- # UNIT TESTS WEBAPP #####################################################
+ # UNIT TESTS WEBAPP ######################################################
##########################################################################
- # TODO: Why do we need those .envs?
- name: backend | copy env files webapp
run: cp webapp/.env.template webapp/.env
- name: backend | copy env files backend
@@ -240,5 +249,93 @@ jobs:
- name: backend | docker-compose
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp
- name: webapp | Unit tests
- #run: docker run --rm ocelotsocialnetwork/webapp:build yarn run test
- run: docker-compose exec -T webapp yarn test
\ No newline at end of file
+ run: docker-compose exec -T webapp yarn test
+ ##########################################################################
+ # COVERAGE REPORT FRONTEND ################################################
+ ##########################################################################
+ #- name: frontend | Coverage report
+ # uses: romeovs/lcov-reporter-action@v0.2.21
+ # with:
+ # github-token: ${{ secrets.GITHUB_TOKEN }}
+ # lcov-file: ./coverage/lcov.info
+ ##########################################################################
+ # COVERAGE CHECK WEBAPP ##################################################
+ ##########################################################################
+ - name: webapp | Coverage check
+ uses: webcraftmedia/coverage-check-action@master
+ with:
+ report_name: Coverage Webapp
+ type: lcov
+ result_path: ./coverage/lcov.info
+ min_coverage: 65
+ token: ${{ github.token }}
+
+ ##############################################################################
+ # JOB: FULLSTACK TESTS #######################################################
+ ##############################################################################
+ fullstack_tests:
+ name: Fullstack tests
+ runs-on: ubuntu-latest
+ needs: [build_test_webapp, build_test_backend, build_test_neo4j]
+ env:
+ jobs: 8
+ strategy:
+ matrix:
+ # run copies of the current job in parallel
+ job: [1, 2, 3, 4, 5, 6, 7, 8]
+ steps:
+ ##########################################################################
+ # CHECKOUT CODE ##########################################################
+ ##########################################################################
+ - name: Checkout code
+ uses: actions/checkout@v2
+ ##########################################################################
+ # DOWNLOAD DOCKER IMAGES #################################################
+ ##########################################################################
+ - name: Download Docker Image (Neo4J)
+ uses: actions/download-artifact@v2
+ with:
+ name: docker-neo4j-image
+ path: /tmp
+ - name: Load Docker Image
+ run: docker load < /tmp/neo4j.tar
+ - name: Download Docker Image (Backend)
+ uses: actions/download-artifact@v2
+ with:
+ name: docker-backend-test
+ path: /tmp
+ - name: Load Docker Image
+ run: docker load < /tmp/backend.tar
+ - name: Download Docker Image (Webapp)
+ uses: actions/download-artifact@v2
+ with:
+ name: docker-webapp-test
+ path: /tmp
+ - name: Load Docker Image
+ run: docker load < /tmp/webapp.tar
+ ##########################################################################
+ # FULLSTACK TESTS CYPRESS ################################################
+ ##########################################################################
+ - name: webapp | copy env files webapp
+ run: cp webapp/.env.template webapp/.env
+ - name: backend | copy env files backend
+ run: cp backend/.env.template backend/.env
+ - name: backend | docker-compose
+ run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend
+ - name: cypress | Fullstack tests
+ run: |
+ yarn install
+ yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }} )
+ ##########################################################################
+ # UPLOAD SCREENSHOTS & VIDEO #############################################
+ ##########################################################################
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: cypress-screenshots
+ path: cypress/screenshots/
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: cypress-videos
+ path: cypress/videos/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09fbb1478..e7e69342a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,16 +4,85 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
-#### [v0.6.15](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.14...v0.6.15)
+#### [v1.0.4](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.3...v1.0.4)
+- fixed wrong env variable [`#4474`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4474)
+- chore: [WIP] 🍰 New Deployment With 'base' And 'code' Docker Images [`#4452`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4452)
+- feat: 🍰 Flexible Footer Links [`#4468`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4468)
+- docs: 🍰 Correct 'Contribution.md' [`#4466`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4466)
+- docs: 🍰 Correct Discord Links And Divers [`#4461`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4461)
+- Implement flexible page footer links [`1bd4af6`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/1bd4af6fd3b5db167575910948a0a72461a1129a)
+- Implement tests for flexible page footer links [`627a20f`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/627a20f66a65450996a5fe3128fd37769fdfd629)
+- Correct Discord links and divers [`0318910`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/0318910488e245c4a1d09181265de63d05a89cf1)
+
+#### [1.0.3](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.2...1.0.3)
+
+> 19 May 2021
+
+- chore: 🍰 Release v1.0.3 [`#4435`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4435)
+- chore: 🍰 Refactor Logos [`#4433`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4433)
+- feat: 🍰 Show Password Component [`#4370`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4370)
+- chore: 🍰 Replace Ocelot Logos 619x593 With 600x570 [`#4428`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4428)
+- Coverage [`#4393`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4393)
+- correct_docker_tagging [`#4391`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4391)
+- 🍰 Get Cypress Tests Running Again [`#4338`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4338)
+- frontend + backend coverage tests [`#4367`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4367)
+- refactor: 🍰 Refactor E-Mail Templates [`#4350`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4350)
+- feat: 🍰 Remove More-Info Of Post [`#4316`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4316)
+- have cypress running locally - the tests still fail [`e3e0341`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/e3e03415e1bd9e0be88f33930a52e63d8af64ee1)
+- have cypress running locally - the tests still fail [`0ec0574`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/0ec05743751cfef0cb86c17b87e9e4ef9a2c9e47)
+- Refactor logos, first step [`ff6cc30`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/ff6cc306aff6150a924f1a647387e498d050ea9f)
+
+#### [1.0.2](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.1...1.0.2)
+
+> 6 April 2021
+
+- fix: Email Confirmation-Link When An Invite-Code Is Given [`#4336`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4336)
+- release v1.0.2 [`e583010`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/e5830101e4b449905fe2d0018627d75af62b2a20)
+- slider jumps to enter-nonce when link contains invite-code, email and nonce and method is invite-code. Thanks @tirokk [`c80b3a2`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/c80b3a212835f76cfc2f11542345d6c3b226995b)
+- fix enail confirmation link when an invite-code is given [`27f0de9`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/27f0de9464685c8f960bde9d07986fdc5b20f8f9)
+
+#### [1.0.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.0...1.0.1)
+
+> 4 April 2021
+
+- readme_update [`#4331`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4331)
+- jwt_expiretime [`#4330`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4330)
+- Clean env, docker & workflow [`#4337`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4337)
+- fix: 🍰 Fixing The Avatars unwanted Border [`#4320`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4320)
+- Change background color of avatar image to white [`e48a99a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/e48a99afe63e96cffafe16db7bf5ae35cfdebd7a)
+- removed config warning [`f6c070a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/f6c070a3cb36c5593123b27b4d1b6a5b7a10aba3)
+- include env files in build process [`dfe6f67`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/dfe6f679b3bb0f440aee986d6d12925cccee9050)
+
+### [1.0.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.15...1.0.0)
+
+> 31 March 2021
+
+- v1.0.0 [`#4321`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4321)
+- fix: 🐛 Fix Wrong Truncation Of Hashtags In Admin List Via Adding 'truncateStr' [`#4314`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4314)
+- feat: 🍰Implement Registration Slider [`#4270`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4270)
+- feat: 🍰 Invite Button [`#4301`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4301)
+- fix: 🍰 Suggestion List Filter [`#4296`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4296)
+- fix: JWT Expires In 2 Years [`#4278`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4278)
+- Deployment [`#4263`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4263)
+- feat: 🍰 Redesign Registration Process Frontend [`#4168`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4168)
+- Delete unnecessary code [`3fa7e04`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/3fa7e04d4895161db1f764ccad58e35188c9d065)
+- basic invite button in frontend [`356f026`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/356f02622655a57561fcdecfcc8a735d23cac79f)
+- setting up invite button [`e6dc3f4`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/e6dc3f42cedaf9953d737cf30cf7ed317b634be7)
+
+#### [0.6.15](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.14...0.6.15)
+
+> 1 March 2021
+
+- feat: Count Post Teaser Views [`#4255`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4255)
- feat: 🍰 Count Post Clicks [`#4248`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4248)
- Correct version style [`#4218`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4218)
- dashboard restructuring image [`#4266`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4266)
- fix: 🐛 Adapted Editor List Styles [`#4239`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4239)
- fix: Scrolling On Profile Page [`#4234`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4234)
+- release 0.6.15 [`ef4265d`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/ef4265d9387d94aa09db2b80461c2ec90b4623ae)
- count views of post teaser [`1c3f628`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/1c3f628fb2e161400319b32da274952c1b57836e)
- tests fixed for clickedCount [`96066ea`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/96066eae02e659a00bce280f9f97a28ac1446ce0)
-- add clickedCount to PostTeaser [`d3eafc9`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/d3eafc9b69c70e31021c6e2723bd5bcfa6b9d17f)
#### [0.6.14](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.13...0.6.14)
@@ -99,21 +168,11 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- file upload: refactored [`650e83f`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/650e83f4c250389477933a2e7d21d8245b0ce882)
- change user role: tests are working [`14dfe2a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/14dfe2ae2cd4a24c06c9229893b33586dfceae4f)
-#### [0.6.5](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/v0.6.4...0.6.5)
+#### [0.6.5](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.4...0.6.5)
> 8 February 2021
-- - adjusted changelog to ocelot-social repo [`9603882`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/9603882edebf8967e05abfa94e4e1ebf452d4e24)
-- - first steps towards docker image deployment & github autotagging [`5503216`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5503216ad4a0230ac533042e4a69806590fc2a5a)
-- - deploy structure image [`a60400b`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/a60400b4fe6f59bbb80e1073db4def3ba205e1a7)
-
-#### [v0.6.4](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.4...v0.6.4)
-
-> 9 February 2021
-
-- chore(release): 0.6.4 [`8b7570d`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/8b7570dc35d0ea431f673a711ac051f1e1320acb)
-- change user roles is working, test fails [`8c3310a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/8c3310abaf87c0e5597fec4f93fb37d27122c9e7)
-- change user role: tests are working [`f10da4b`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/f10da4b09388fe1e2b85abd53f6ffc67c785d4c1)
+- updated CHANGELOG.md [`9d9075f`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/9d9075f2117b2eb4b607e7d59ab18c7e655c6ea7)
#### [0.6.4](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.3...0.6.4)
@@ -123,15 +182,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- fetch full history [`5ecee4d`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5ecee4d73a92d2e5c5ae971d79848ed27f65a72c)
- don't fail if tag exists (release) [`39c82fc`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/39c82fcb37d5c8e7e78a79288e1ef6280f8d0892)
-#### [0.6.3](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/v0.6.3...0.6.3)
-
-> 8 February 2021
-
-- - adjusted changelog to ocelot-social repo [`9603882`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/9603882edebf8967e05abfa94e4e1ebf452d4e24)
-- - fixed changelog [`cf70b12`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/cf70b12ed74011924ea788ab932fc9d7ac0e6bd9)
-- - yarn install to allow yarn auto-changelog [`fc496aa`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/fc496aa04cb7e804da4335da0cb5cda26f874ea2)
-
-#### [v0.6.3](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.0...v0.6.3)
+#### [0.6.3](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.0...0.6.3)
> 8 February 2021
@@ -140,9 +191,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- feat: 🍰 Allow Only Supported Image File Formats [`#3928`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/3928)
- refactor: Disbale Emoji, Language And Catgeory Filter [`#4193`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4193)
- refactor: Remove Catgeories From Post Teaser [`#4191`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4191)
+- - adjusted changelog to ocelot-social repo [`9603882`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/9603882edebf8967e05abfa94e4e1ebf452d4e24)
- - first steps towards docker image deployment & github autotagging [`5503216`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5503216ad4a0230ac533042e4a69806590fc2a5a)
- - lots of additional tests [`0ba37aa`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/0ba37aab18f537d722aede7b87fa0b8e79f80e66)
-- - deploy structure image [`a60400b`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/a60400b4fe6f59bbb80e1073db4def3ba205e1a7)
#### [0.6.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/v0.6.0...0.6.0)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b5f74d879..50d3721d1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,31 +4,33 @@ Thank you so much for thinking of contributing to the Human Connection project!
## Getting Set Up
-Instructions for how to install all the necessary software and some code guidelines can be found in our [documentation](https://docs.human-connection.org/human-connection/).
+Instructions for how to install all the necessary software and some code guidelines can be found in our main [Readme](/README.md) or in our [documentation](https://docs.human-connection.org/human-connection/).
-To get you started we recommend that you join forces with a regular contributor. Please join [our discord instance](https://human-connection.org/discord) to chat with developers or just get in touch directly on an issue on either [Github](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues) or [Zenhub](https://app.zenhub.com/workspaces/ocelotsocial-5fb21ff922cb410015dd6535/board?filterLogic=any&repos=301151089):
+To get you started we recommend that you join forces with a regular contributor. Please join [our Discord instance](https://discord.gg/AJSX9DCSUA) to chat with developers or just get in touch directly on an issue on either [Github](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues) or [Zenhub](https://app.zenhub.com/workspaces/ocelotsocial-5fb21ff922cb410015dd6535/board?filterLogic=any&repos=301151089):

-We also have regular pair programming sessions that you are very welcome to join! We feel this is often the best way to get to know both the project and the team. Most developers are also available for spontaneous sessions if the times listed below don't work for you – just ping us on discord.
+We also have regular pair programming sessions that you are very welcome to join! We feel this is often the best way to get to know both the project and the team. Most developers are also available for spontaneous sessions if the times listed below don't work for you – just ping us on Discord.
## Development Flow
We operate in two week sprints that are planned, estimated and prioritised on [Zenhub](https://app.zenhub.com/workspaces/ocelotsocial-5fb21ff922cb410015dd6535/board?filterLogic=any&repos=301151089). All issues are also linked to and synced with [Github](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues). Look for the `good first issue` label if you're not sure where to start!
-We try to discuss all questions directly related to a feature or bug in the respective issue, in order to preserve it for the future and for other developers. We use discord for real-time communication.
+We try to discuss all questions directly related to a feature or bug in the respective issue, in order to preserve it for the future and for other developers. We use Discord for real-time communication.
This is how we solve bugs and implement features, step by step:
+
1. We find an issue we want to work on, usually during the sprint planning but as an open source contributor this can happen at any time.
2. We communicate with the team to see if the issue is still available. (When you comment on an issue but don't get an answer there within 1-2 days try to mention @Human-Connection/hc-dev-team to make sure we check in.)
3. We make sure we understand the issue in detail – what problem is it solving and how should it be implemented?
-4. We assign ourselves to the issue and move it to `In Progress` on [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f).
+4. We assign ourselves to the issue and move it to `In Progress` on [Zenhub](https://app.zenhub.com/workspaces/ocelotsocial-5fb21ff922cb410015dd6535/board?filterLogic=any&repos=301151089).
5. We start working on it in a `new branch` and open a `pull request` prefixed with `[WIP]` (work in progress) to which we regularly push our changes.
6. When questions come up we clarify them with the team (directly in the issue on Github).
7. When we are happy with our work and our PR is passing all tests we remove the `[WIP]` from the PR description and ask for reviews (if you're not sure who to ask there is @Human-Connection/hc-dev-team which pings all core developers).
8. We then incorporate the suggestions from the reviews into our work and once it has been approved it can be merged into master!
Every pull request needs to:
+
* fix an issue (if there is something you want to work on but there is no issue for it, create one first and discuss it with the team)
* include tests for the code that is added or changed
* pass all tests (linter, backend, frontend, end-to-end)
@@ -38,37 +40,46 @@ Every pull request needs to:
There are many volunteers all around the world helping us build this network and without their contributions we wouldn't be where we are today. Big thank you to all of you!
-You can see the core team behind Human Connection [on our website](https://human-connection.org/en/the-team/). On Github you will mostly run into our developers:
-* Robert (@roschaefer)
-* Matt (@mattwr18)
+You can talk to our core team on [Discord](https://discord.gg/AJSX9DCSUA). And on Github you will mostly run into our core developers:
+
+* Ulf (@ulfgebhardt)
+* Moriz (@Mogge)
* Wolle (@Tirokk)
* Alex (@ogerly)
+
+
## Meetings and Pair Programming Sessions
Times below refer to **German Time** – that's CET (GMT+1) in winter and CEST (GMT+2) in summer – because most Human Connection core team members are living in Germany.
Daily standup
-* every Monday–Friday 11:30
-* in the discord `Conference Room`
+
+* every Monday–Thursday 11:30 am (german time see above 👆🏼)
+* in our [Discord](https://discord.gg/AJSX9DCSUA) `Office Cube`
* all contributors welcome!
* everybody shares what they are working on and asks for help if they are blocked
+
## Philosophy
@@ -102,10 +115,9 @@ We use pair programming sessions as a tool for knowledge sharing. We can learn a
As a volunteeer you have no commitment except your own self development and your awesomeness by contributing to this free and open-source software project. Cheers to you!
-
## Open-Source Bounties
-There are so many good reasons to contribute to Human Connection
+There are so many good reasons to contribute to ocelot.social
* You learn state-of-the-art technologies
* You build your portfolio
@@ -121,7 +133,7 @@ pull request approved and merged for free**. You can choose something really
quick and easy. What's important is starting a working relationship with the
team, learning the workflow, and understanding this contribution guide. You can
filter issues by 'good first issue', to get an idea where to start. Please join
-our our [community chat](https://human-connection.org/discord), too.
+our our [Discord community chat](https://discord.gg/AJSX9DCSUA), too.
You can filter Github issues with label [bounty](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues?q=is%3Aopen+is%3Aissue+label%3Abounty). These issues should have a second label `€`
which indicate their respective financial compensation in Euros.
diff --git a/README.md b/README.md
index 8f1abf6ea..416fcaefb 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,13 @@ ocelot.social is a nonprofit social, action and knowledge network that connects
* **Knowledge**: Read articles about interesting topics and find related posts in the **More Info** tab or by **Filtering** based on **Categories** and **Tagging** or by using the **Fulltext Search**.
* **Action**: Don't just read about how to make the world a better place, but come into **Action** by following provided suggestions on the **Action** tab provided by other people or **Organisations**.
- [](https://ocelot.social)
+
+
+
## Live demo
-Try out our deployed [development environment](https://develop.human-connection.org/).
+__Try out our deployed [development environment](https://develop.human-connection.org/).__
Logins:
@@ -29,9 +31,9 @@ Logins:
## Directory Layout
There are four important directories:
+
* [Backend](./backend) runs on the server and is a middleware between database and frontend
* [Frontend](./webapp) is a server-side-rendered and client-side-rendered web frontend
-* [Deployment](./deployment) configuration for kubernetes
* [Cypress](./cypress) contains end-to-end tests and executable feature specifications
In order to setup the application and start to develop features you have to
@@ -45,16 +47,19 @@ There are two approaches:
## Installation
### Clone the Repository
+
Clone the repository, this will create a new folder called `Ocelot-Social`:
Using HTTPS:
+
```bash
$ git clone https://github.com/Ocelot-Social-Community/Ocelot-Social.git
```
Using SSH:
+
```bash
-$ git clone git@github.com:Human-Connection/Human-Connection.git
+$ git clone git@github.com:Ocelot-Social-Community/Ocelot-Social.git
```
Change into the new folder.
@@ -87,25 +92,33 @@ docker-compose version 1.23.2
#### Start Ocelot-Social via Docker-Compose
For Development:
+
```bash
-docker-compose up
+$ docker-compose up
```
-For Production
+For Production:
+
```bash
-docker-compose -f docker-compose.yml up
+$ docker-compose -f docker-compose.yml up
```
This will start all required Docker containers
+## 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 is `docker-compose` for development purposes as described above.
+
## Developer Chat
-Join our friendly open-source community on [Discord](https://discordapp.com/invite/DFSjPaX) :heart_eyes_cat:
+Join our friendly open-source community on [Discord](https://discord.gg/AJSX9DCSUA) :heart_eyes_cat:
Just introduce yourself at `#introduce-yourself` and mention `@@Mentor` to get you onboard :neckbeard:
Check out the [contribution guideline](./CONTRIBUTING.md), too!
We give write permissions to every developer who asks for it. Just text us on
-[Discord](https://discord.gg/6ub73U3).
+[Discord](https://discord.gg/AJSX9DCSUA).
## Technology Stack
@@ -124,4 +137,5 @@ Browser compatibility testing with [BrowserStack](https://www.browserstack.com/)
## License
+
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
diff --git a/SUMMARY.md b/SUMMARY.md
index a04c96d98..9c74b1974 100644
--- a/SUMMARY.md
+++ b/SUMMARY.md
@@ -15,24 +15,8 @@
* [End-to-end tests](cypress/README.md)
* [Frontend tests](webapp/testing.md)
* [Backend tests](backend/testing.md)
+* [Deployment](https://github.com/Ocelot-Social-Community/Ocelot-Social-Deploy-Rebranding/blob/master/deployment/README.md)
* [Contributing](CONTRIBUTING.md)
-* [Kubernetes Deployment](deployment/README.md)
- * [Minikube](deployment/minikube/README.md)
- * [Digital Ocean](deployment/digital-ocean/README.md)
- * [Kubernetes Dashboard](deployment/digital-ocean/dashboard/README.md)
- * [HTTPS](deployment/digital-ocean/https/README.md)
- * [ocelot.social](deployment/ocelot-social/README.md)
- * [Error Reporting](deployment/ocelot-social/error-reporting/README.md)
- * [Mailserver](deployment/ocelot-social/mailserver/README.md)
- * [Maintenance](deployment/ocelot-social/maintenance/README.md)
- * [Volumes](deployment/volumes/README.md)
- * [Neo4J Offline-Backups](deployment/volumes/neo4j-offline-backup/README.md)
- * [Neo4J Online-Backups](deployment/volumes/neo4j-online-backup/README.md)
- * [Volume Snapshots](deployment/volumes/volume-snapshots/README.md)
- * [Reclaim Policy](deployment/volumes/reclaim-policy/README.md)
- * [Velero](deployment/volumes/velero/README.md)
- * [Metrics](deployment/monitoring/README.md)
- * [Legacy Migration](deployment/legacy-migration/README.md)
* [Feature Specification](cypress/features.md)
* [Code of conduct](CODE_OF_CONDUCT.md)
* [License](LICENSE.md)
diff --git a/backend/.env.template b/backend/.env.template
index fc9766478..5858a5d1e 100644
--- a/backend/.env.template
+++ b/backend/.env.template
@@ -10,6 +10,7 @@ SMTP_USERNAME=
SMTP_PASSWORD=
JWT_SECRET="b/&&7b78BF&fv/Vd"
+JWT_EXPIRES="2y"
MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g"
PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
@@ -17,6 +18,7 @@ PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
SENTRY_DSN_BACKEND=
COMMIT=
PUBLIC_REGISTRATION=false
+INVITE_REGISTRATION=true
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
diff --git a/backend/Dockerfile b/backend/Dockerfile
index 2f2b70f04..0ebdfb1eb 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -1,17 +1,20 @@
##################################################################################
-# BASE ###########################################################################
+# BASE (Is pushed to DockerHub for rebranding) ###################################
##################################################################################
FROM node:12.19.0-alpine3.10 as base
-# ENVs (available in production aswell, can be overwritten by commandline or env file)
+# ENVs
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
ENV DOCKER_WORKDIR="/app"
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
-ENV BUILD_DATE="1970-01-01T00:00:00.00Z"
+ARG BBUILD_DATE="1970-01-01T00:00:00.00Z"
+ENV BUILD_DATE=$BBUILD_DATE
## We cannot do $(yarn run version)-${BUILD_NUMBER} here so we default to 0.0.0-0
-ENV BUILD_VERSION="0.0.0-0"
+ARG BBUILD_VERSION="0.0.0-0"
+ENV BUILD_VERSION=$BBUILD_VERSION
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
-ENV BUILD_COMMIT="0000000"
+ARG BBUILD_COMMIT="0000000"
+ENV BUILD_COMMIT=$BBUILD_COMMIT
## SET NODE_ENV
ENV NODE_ENV="production"
## App relevant Envs
@@ -56,12 +59,18 @@ FROM base as development
CMD /bin/sh -c "yarn install && yarn run dev"
##################################################################################
-# BUILD (Does contain all files and is therefore bloated) ########################
+# CODE (Does contain all code files and is pushed to DockerHub for rebranding) ###
##################################################################################
-FROM base as build
+FROM base as code
-# Copy everything
+# copy everything, but do not build.
COPY . .
+
+##################################################################################
+# BUILD (Does contain all files and the compilate and is therefore bloated) ######
+##################################################################################
+FROM code as build
+
# yarn install
RUN yarn install --production=false --frozen-lockfile --non-interactive
# yarn build
@@ -82,7 +91,6 @@ FROM base as production
# Copy "binary"-files from build image
COPY --from=build ${DOCKER_WORKDIR}/dist ./dist
-COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
# Copy static files
# TODO - externalize the uploads so we can copy the whole folder
COPY --from=build ${DOCKER_WORKDIR}/public/img/ ./public/img/
@@ -91,4 +99,4 @@ COPY --from=build ${DOCKER_WORKDIR}/public/providers.json ./public/providers.jso
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
# Run command
-CMD /bin/sh -c "yarn run start"
\ No newline at end of file
+CMD /bin/sh -c "yarn run start"
diff --git a/backend/package.json b/backend/package.json
index ff448e293..ac5c1d2f8 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "ocelot-social-backend",
- "version": "0.6.15",
+ "version": "1.0.4",
"description": "GraphQL Backend for ocelot.social",
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
"author": "ocelot.social Community",
@@ -15,7 +15,7 @@
"dev": "nodemon --exec babel-node src/ -e js,gql",
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql",
"lint": "eslint src --config .eslintrc.js",
- "test": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --runInBand",
+ "test": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --runInBand --coverage",
"db:clean": "babel-node src/db/clean.js",
"db:reset": "yarn run db:clean",
"db:seed": "babel-node src/db/seed.js",
@@ -39,6 +39,12 @@
]
},
"dependencies": {
+ "@babel/cli": "~7.8.4",
+ "@babel/core": "~7.9.0",
+ "@babel/node": "~7.8.7",
+ "@babel/plugin-proposal-throw-expressions": "^7.8.3",
+ "@babel/preset-env": "~7.9.5",
+ "@babel/register": "^7.9.0",
"@hapi/joi": "^17.1.1",
"@sentry/node": "^5.15.4",
"apollo-cache-inmemory": "~1.6.5",
@@ -48,12 +54,15 @@
"apollo-server": "~2.14.2",
"apollo-server-express": "^2.14.2",
"aws-sdk": "^2.652.0",
+ "babel-core": "~7.0.0-0",
+ "babel-eslint": "~10.1.0",
+ "babel-jest": "~25.2.6",
"babel-plugin-transform-runtime": "^6.23.0",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
"cross-env": "~7.0.2",
- "date-fns": "2.11.1",
+ "date-fns": "2.22.1",
"debug": "~4.1.1",
"dotenv": "~8.2.0",
"express": "^4.17.1",
@@ -72,7 +81,7 @@
"languagedetect": "^2.0.0",
"linkifyjs": "~2.1.8",
"lodash": "~4.17.14",
- "merge-graphql-schemas": "^1.7.7",
+ "merge-graphql-schemas": "^1.7.8",
"metascraper": "^5.11.8",
"metascraper-audio": "^5.14.26",
"metascraper-author": "^5.14.22",
@@ -92,7 +101,7 @@
"migrate": "^1.7.0",
"mime-types": "^2.1.26",
"minimatch": "^3.0.4",
- "mustache": "^4.0.1",
+ "mustache": "^4.2.0",
"neo4j-driver": "^4.0.2",
"neo4j-graphql-js": "^2.11.5",
"neode": "^0.3.7",
@@ -111,16 +120,7 @@
"xregexp": "^4.3.0"
},
"devDependencies": {
- "@babel/cli": "~7.8.4",
- "@babel/core": "~7.9.0",
- "@babel/node": "~7.8.7",
- "@babel/plugin-proposal-throw-expressions": "^7.8.3",
- "@babel/preset-env": "~7.9.5",
- "@babel/register": "^7.9.0",
"apollo-server-testing": "~2.11.0",
- "babel-core": "~7.0.0-0",
- "babel-eslint": "~10.1.0",
- "babel-jest": "~25.2.6",
"chai": "~4.2.0",
"cucumber": "~6.0.5",
"eslint": "~6.8.0",
@@ -134,7 +134,7 @@
"eslint-plugin-standard": "~4.0.1",
"jest": "~25.3.0",
"nodemon": "~2.0.2",
- "prettier": "~2.2.0",
+ "prettier": "~2.3.2",
"rosie": "^2.0.1",
"supertest": "~4.0.2"
},
diff --git a/backend/src/config/index.js b/backend/src/config/index.js
index 47771029b..f3a8fb63c 100644
--- a/backend/src/config/index.js
+++ b/backend/src/config/index.js
@@ -7,9 +7,8 @@ if (require.resolve) {
try {
dotenv.config({ path: require.resolve('../../.env') })
} catch (error) {
- if (error.code === 'MODULE_NOT_FOUND') {
- console.log('WARN: No `.env` file found in `/app` (docker) or `/backend` (no docker)') // eslint-disable-line no-console
- } else {
+ // This error is thrown when the .env is not found
+ if (error.code !== 'MODULE_NOT_FOUND') {
throw error
}
}
@@ -35,13 +34,14 @@ const required = {
const server = {
CLIENT_URI: env.CLIENT_URI || 'http://localhost:3000',
GRAPHQL_URI: env.GRAPHQL_URI || 'http://localhost:4000',
+ JWT_EXPIRES: env.JWT_EXPIRES || '2y',
}
const smtp = {
SMTP_HOST: env.SMTP_HOST,
SMTP_PORT: env.SMTP_PORT,
- SMTP_IGNORE_TLS: env.SMTP_IGNORE_TLS === 'true' || true,
- SMTP_SECURE: env.SMTP_IGNORE_TLS === 'true' || false,
+ SMTP_IGNORE_TLS: env.SMTP_IGNORE_TLS !== 'false', // default = true
+ SMTP_SECURE: env.SMTP_SECURE === 'true',
SMTP_USERNAME: env.SMTP_USERNAME,
SMTP_PASSWORD: env.SMTP_PASSWORD,
}
@@ -82,7 +82,8 @@ const options = {
SUPPORT_URL: links.SUPPORT,
APPLICATION_NAME: metadata.APPLICATION_NAME,
ORGANIZATION_URL: links.ORGANIZATION,
- PUBLIC_REGISTRATION: env.PUBLIC_REGISTRATION === 'true',
+ PUBLIC_REGISTRATION: env.PUBLIC_REGISTRATION === 'true' || false,
+ INVITE_REGISTRATION: env.INVITE_REGISTRATION !== 'false', // default = true
}
// Check if all required configs are present
diff --git a/backend/src/config/links.js b/backend/src/config/links.js
index 494e449ba..6b945a5e0 100644
--- a/backend/src/config/links.js
+++ b/backend/src/config/links.js
@@ -1,6 +1,13 @@
+// this file is duplicated in `backend/src/config/links.js` and `webapp/constants/links.js` and replaced on rebranding
export default {
ORGANIZATION: 'https://ocelot.social',
- DONATE: 'https://ocelot-social.herokuapp.com/donations',
- FAQ: 'https://ocelot.social',
SUPPORT: 'https://ocelot.social',
+
+ // on null or empty strings internal imprint is used, see 'webapp/locales/html/'
+ DONATE: 'https://ocelot-social.herokuapp.com/donations', // we use 'ocelot-social.herokuapp.com' at the moment, because redirections of 'ocelot.social' subpages are not working correctly
+ IMPRINT: 'https://ocelot-social.herokuapp.com/imprint', // we use 'ocelot-social.herokuapp.com' at the moment, because redirections of 'ocelot.social' subpages are not working correctly
+ TERMS_AND_CONDITIONS: null,
+ CODE_OF_CONDUCT: null,
+ DATA_PRIVACY: null,
+ FAQ: 'https://ocelot.social',
}
diff --git a/backend/src/config/logos.js b/backend/src/config/logos.js
new file mode 100644
index 000000000..d093c7b46
--- /dev/null
+++ b/backend/src/config/logos.js
@@ -0,0 +1,10 @@
+// this file is duplicated in `backend/src/config/logos.js` and `webapp/constants/logos.js` and replaced on rebranding
+// this are the paths in the webapp
+export default {
+ LOGO_HEADER_PATH: '/img/custom/logo-horizontal.svg',
+ LOGO_SIGNUP_PATH: '/img/custom/logo-squared.svg',
+ LOGO_WELCOME_PATH: '/img/custom/logo-squared.svg',
+ LOGO_LOGOUT_PATH: '/img/custom/logo-squared.svg',
+ LOGO_PASSWORD_RESET_PATH: '/img/custom/logo-squared.svg',
+ LOGO_MAINTENACE_RESET_PATH: '/img/custom/logo-squared.svg',
+}
diff --git a/backend/src/config/metadata.js b/backend/src/config/metadata.js
index 68d353eea..d40308e80 100644
--- a/backend/src/config/metadata.js
+++ b/backend/src/config/metadata.js
@@ -1,7 +1,9 @@
+// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js` and replaced on rebranding
export default {
APPLICATION_NAME: 'ocelot.social',
APPLICATION_SHORT_NAME: 'ocelot',
APPLICATION_DESCRIPTION: 'ocelot.social Community Network',
+ COOKIE_NAME: 'ocelot-social-token',
ORGANIZATION_NAME: 'ocelot.social Community',
ORGANIZATION_JURISDICTION: 'City of Angels',
}
diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js
index 717926413..bedf592ed 100644
--- a/backend/src/db/factories.js
+++ b/backend/src/db/factories.js
@@ -105,12 +105,12 @@ Factory.define('user')
})
Factory.define('post')
- .option('categoryIds', [])
+ /* .option('categoryIds', [])
.option('categories', ['categoryIds'], (categoryIds) => {
if (categoryIds.length) return Promise.all(categoryIds.map((id) => neode.find('Category', id)))
// there must be at least one category
return Promise.all([Factory.build('category')])
- })
+ }) */
.option('tagIds', [])
.option('tags', ['tagIds'], (tagIds) => {
return Promise.all(tagIds.map((id) => neode.find('Tag', id)))
@@ -147,16 +147,16 @@ Factory.define('post')
return language || 'en'
})
.after(async (buildObject, options) => {
- const [post, author, image, categories, tags] = await Promise.all([
+ const [post, author, image, /* categories, */ tags] = await Promise.all([
neode.create('Post', buildObject),
options.author,
options.image,
- options.categories,
+ // options.categories,
options.tags,
])
await Promise.all([
post.relateTo(author, 'author'),
- Promise.all(categories.map((c) => c.relateTo(post, 'post'))),
+ // Promise.all(categories.map((c) => c.relateTo(post, 'post'))),
Promise.all(tags.map((t) => t.relateTo(post, 'post'))),
])
if (image) await post.relateTo(image, 'image')
diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js
index 713a03142..7d9b5e954 100644
--- a/backend/src/db/seed.js
+++ b/backend/src/db/seed.js
@@ -137,100 +137,93 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
}),
])
- const [
- peterLustig,
- bobDerBaumeister,
- jennyRostock,
- huey,
- dewey,
- louie,
- dagobert,
- ] = await Promise.all([
- Factory.build(
- 'user',
- {
- id: 'u1',
- name: 'Peter Lustig',
- slug: 'peter-lustig',
- role: 'admin',
- },
- {
- email: 'admin@example.org',
- },
- ),
- Factory.build(
- 'user',
- {
- id: 'u2',
- name: 'Bob der Baumeister',
- slug: 'bob-der-baumeister',
- role: 'moderator',
- },
- {
- email: 'moderator@example.org',
- },
- ),
- Factory.build(
- 'user',
- {
- id: 'u3',
- name: 'Jenny Rostock',
- slug: 'jenny-rostock',
- role: 'user',
- },
- {
- email: 'user@example.org',
- },
- ),
- Factory.build(
- 'user',
- {
- id: 'u4',
- name: 'Huey',
- slug: 'huey',
- role: 'user',
- },
- {
- email: 'huey@example.org',
- },
- ),
- Factory.build(
- 'user',
- {
- id: 'u5',
- name: 'Dewey',
- slug: 'dewey',
- role: 'user',
- },
- {
- email: 'dewey@example.org',
- },
- ),
- Factory.build(
- 'user',
- {
- id: 'u6',
- name: 'Louie',
- slug: 'louie',
- role: 'user',
- },
- {
- email: 'louie@example.org',
- },
- ),
- Factory.build(
- 'user',
- {
- id: 'u7',
- name: 'Dagobert',
- slug: 'dagobert',
- role: 'user',
- },
- {
- email: 'dagobert@example.org',
- },
- ),
- ])
+ const [peterLustig, bobDerBaumeister, jennyRostock, huey, dewey, louie, dagobert] =
+ await Promise.all([
+ Factory.build(
+ 'user',
+ {
+ id: 'u1',
+ name: 'Peter Lustig',
+ slug: 'peter-lustig',
+ role: 'admin',
+ },
+ {
+ email: 'admin@example.org',
+ },
+ ),
+ Factory.build(
+ 'user',
+ {
+ id: 'u2',
+ name: 'Bob der Baumeister',
+ slug: 'bob-der-baumeister',
+ role: 'moderator',
+ },
+ {
+ email: 'moderator@example.org',
+ },
+ ),
+ Factory.build(
+ 'user',
+ {
+ id: 'u3',
+ name: 'Jenny Rostock',
+ slug: 'jenny-rostock',
+ role: 'user',
+ },
+ {
+ email: 'user@example.org',
+ },
+ ),
+ Factory.build(
+ 'user',
+ {
+ id: 'u4',
+ name: 'Huey',
+ slug: 'huey',
+ role: 'user',
+ },
+ {
+ email: 'huey@example.org',
+ },
+ ),
+ Factory.build(
+ 'user',
+ {
+ id: 'u5',
+ name: 'Dewey',
+ slug: 'dewey',
+ role: 'user',
+ },
+ {
+ email: 'dewey@example.org',
+ },
+ ),
+ Factory.build(
+ 'user',
+ {
+ id: 'u6',
+ name: 'Louie',
+ slug: 'louie',
+ role: 'user',
+ },
+ {
+ email: 'louie@example.org',
+ },
+ ),
+ Factory.build(
+ 'user',
+ {
+ id: 'u7',
+ name: 'Dagobert',
+ slug: 'dagobert',
+ role: 'user',
+ },
+ {
+ email: 'dagobert@example.org',
+ },
+ ),
+ ])
await Promise.all([
peterLustig.relateTo(Berlin, 'isIn'),
@@ -561,7 +554,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
const hashtagAndMention1 =
'The new physics of #QuantenFlussTheorie can explain #QuantumGravity! @peter-lustig got that already. ;-)'
const createPostMutation = gql`
- mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) {
+ mutation ($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) {
CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
id
}
@@ -618,7 +611,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
const mentionInComment2 =
'Did @peter-lustig tell you?'
const createCommentMutation = gql`
- mutation($id: ID, $postId: ID!, $content: String!) {
+ mutation ($id: ID, $postId: ID!, $content: String!) {
CreateComment(id: $id, postId: $postId, content: $content) {
id
}
diff --git a/backend/src/jwt/encode.js b/backend/src/jwt/encode.js
index 9126f2577..baeb62d3d 100644
--- a/backend/src/jwt/encode.js
+++ b/backend/src/jwt/encode.js
@@ -5,7 +5,7 @@ import CONFIG from './../config'
export default function encode(user) {
const { id, name, slug } = user
const token = jwt.sign({ id, name, slug }, CONFIG.JWT_SECRET, {
- expiresIn: '1d',
+ expiresIn: CONFIG.JWT_EXPIRES,
issuer: CONFIG.GRAPHQL_URI,
audience: CONFIG.CLIENT_URI,
subject: user.id.toString(),
diff --git a/backend/src/middleware/email/emailMiddleware.js b/backend/src/middleware/email/emailMiddleware.js
index 1143e3d0f..571b733d5 100644
--- a/backend/src/middleware/email/emailMiddleware.js
+++ b/backend/src/middleware/email/emailMiddleware.js
@@ -43,9 +43,14 @@ if (!hasEmailConfig) {
}
const sendSignupMail = async (resolve, root, args, context, resolveInfo) => {
+ const { inviteCode } = args
const response = await resolve(root, args, context, resolveInfo)
const { email, nonce } = response
- await sendMail(signupTemplate({ email, nonce }))
+ if (inviteCode) {
+ await sendMail(signupTemplate({ email, nonce, inviteCode }))
+ } else {
+ await sendMail(signupTemplate({ email, nonce }))
+ }
delete response.nonce
return response
}
@@ -71,6 +76,5 @@ export default {
AddEmailAddress: sendEmailVerificationMail,
requestPasswordReset: sendPasswordResetMail,
Signup: sendSignupMail,
- SignupByInvitation: sendSignupMail,
},
}
diff --git a/backend/src/middleware/email/templateBuilder.js b/backend/src/middleware/email/templateBuilder.js
index 6e147d752..872b86b29 100644
--- a/backend/src/middleware/email/templateBuilder.js
+++ b/backend/src/middleware/email/templateBuilder.js
@@ -1,10 +1,11 @@
import mustache from 'mustache'
import CONFIG from '../../config'
+import logosWebapp from '../../config/logos.js'
import * as templates from './templates'
const from = CONFIG.EMAIL_DEFAULT_SENDER
-const welcomeImageUrl = new URL(`/img/custom/welcome.svg`, CONFIG.CLIENT_URI)
+const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI)
const defaultParams = {
supportUrl: CONFIG.SUPPORT_URL,
@@ -13,11 +14,18 @@ const defaultParams = {
welcomeImageUrl,
}
-export const signupTemplate = ({ email, nonce }) => {
+export const signupTemplate = ({ email, nonce, inviteCode = null }) => {
const subject = `Willkommen, Bienvenue, Welcome to ${CONFIG.APPLICATION_NAME}!`
- const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI)
- actionUrl.searchParams.set('nonce', nonce)
+ // dev format example: http://localhost:3000/registration?method=invite-mail&email=wolle.huss%40pjannto.com&nonce=64853
+ const actionUrl = new URL('/registration', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('email', email)
+ actionUrl.searchParams.set('nonce', nonce)
+ if (inviteCode) {
+ actionUrl.searchParams.set('inviteCode', inviteCode)
+ actionUrl.searchParams.set('method', 'invite-code')
+ } else {
+ actionUrl.searchParams.set('method', 'invite-mail')
+ }
return {
from,
@@ -34,8 +42,8 @@ export const signupTemplate = ({ email, nonce }) => {
export const emailVerificationTemplate = ({ email, nonce, name }) => {
const subject = 'Neue E-Mail Adresse | New E-Mail Address'
const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI)
- actionUrl.searchParams.set('nonce', nonce)
actionUrl.searchParams.set('email', email)
+ actionUrl.searchParams.set('nonce', nonce)
return {
from,
@@ -77,7 +85,7 @@ export const wrongAccountTemplate = ({ email }) => {
subject,
html: mustache.render(
templates.layout,
- { actionUrl, supportUrl: CONFIG.SUPPORT_URL, welcomeImageUrl },
+ { ...defaultParams, actionUrl, supportUrl: CONFIG.SUPPORT_URL, welcomeImageUrl },
{ content: templates.wrongAccount },
),
}
diff --git a/backend/src/middleware/email/templates/emailVerification.html b/backend/src/middleware/email/templates/emailVerification.html
index 3fc421dc5..35ce27e5a 100644
--- a/backend/src/middleware/email/templates/emailVerification.html
+++ b/backend/src/middleware/email/templates/emailVerification.html
@@ -7,8 +7,8 @@
`
const postWithHastagsQuery = gql`
- query($id: ID) {
+ query ($id: ID) {
Post(id: $id) {
tags {
id
diff --git a/backend/src/middleware/languages/languages.spec.js b/backend/src/middleware/languages/languages.spec.js
index 1448e4e4b..9bba45c0c 100644
--- a/backend/src/middleware/languages/languages.spec.js
+++ b/backend/src/middleware/languages/languages.spec.js
@@ -29,7 +29,7 @@ afterAll(async () => {
})
const createPostMutation = gql`
- mutation($title: String!, $content: String!, $categoryIds: [ID]) {
+ mutation ($title: String!, $content: String!, $categoryIds: [ID]) {
CreatePost(title: $title, content: $content, categoryIds: $categoryIds) {
language
}
diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.js b/backend/src/middleware/notifications/notificationsMiddleware.spec.js
index af4ed9693..7583a6727 100644
--- a/backend/src/middleware/notifications/notificationsMiddleware.spec.js
+++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.js
@@ -10,7 +10,7 @@ const driver = getDriver()
const neode = getNeode()
const categoryIds = ['cat9']
const createPostMutation = gql`
- mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
+ mutation ($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
CreatePost(id: $id, title: $title, content: $postContent, categoryIds: $categoryIds) {
id
title
@@ -19,7 +19,7 @@ const createPostMutation = gql`
}
`
const updatePostMutation = gql`
- mutation($id: ID!, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
+ mutation ($id: ID!, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
UpdatePost(id: $id, content: $postContent, title: $title, categoryIds: $categoryIds) {
title
content
@@ -27,7 +27,7 @@ const updatePostMutation = gql`
}
`
const createCommentMutation = gql`
- mutation($id: ID, $postId: ID!, $commentContent: String!) {
+ mutation ($id: ID, $postId: ID!, $commentContent: String!) {
CreateComment(id: $id, postId: $postId, content: $commentContent) {
id
content
@@ -80,7 +80,7 @@ afterEach(async () => {
describe('notifications', () => {
const notificationQuery = gql`
- query($read: Boolean) {
+ query ($read: Boolean) {
notifications(read: $read, orderBy: updatedAt_desc) {
read
reason
@@ -367,7 +367,7 @@ describe('notifications', () => {
describe('if the notification was marked as read earlier', () => {
const markAsReadAction = async () => {
const mutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
markAsRead(id: $id) {
read
}
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index 7aeb7252a..b10389f50 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -1,6 +1,7 @@
import { rule, shield, deny, allow, or } from 'graphql-shield'
import { getNeode } from '../db/neo4j'
import CONFIG from '../config'
+import { validateInviteCode } from '../schema/resolvers/transactions/inviteCodes'
const debug = !!CONFIG.DEBUG
const allowExternalErrors = true
@@ -87,7 +88,14 @@ const noEmailFilter = rule({
return !('email' in args)
})
-const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION)
+const publicRegistration = rule()(() => CONFIG.PUBLIC_REGISTRATION)
+
+const inviteRegistration = rule()(async (_parent, args, { user, driver }) => {
+ if (!CONFIG.INVITE_REGISTRATION) return false
+ const { inviteCode } = args
+ const session = driver.session()
+ return validateInviteCode(session, inviteCode)
+})
// Permissions
export default shield(
@@ -121,14 +129,15 @@ export default shield(
userData: isAuthenticated,
MyInviteCodes: isAuthenticated,
isValidInviteCode: allow,
+ VerifyNonce: allow,
queryLocations: isAuthenticated,
availableRoles: isAdmin,
+ getInviteCode: isAuthenticated, // and inviteRegistration
},
Mutation: {
'*': deny,
login: allow,
- SignupByInvitation: allow,
- Signup: or(publicRegistration, isAdmin),
+ Signup: or(publicRegistration, inviteRegistration, isAdmin),
SignupVerification: allow,
UpdateUser: onlyYourself,
CreatePost: isAuthenticated,
diff --git a/backend/src/middleware/permissionsMiddleware.spec.js b/backend/src/middleware/permissionsMiddleware.spec.js
index 775533867..5fa4a8f01 100644
--- a/backend/src/middleware/permissionsMiddleware.spec.js
+++ b/backend/src/middleware/permissionsMiddleware.spec.js
@@ -3,11 +3,13 @@ import createServer from '../server'
import Factory, { cleanDatabase } from '../db/factories'
import { gql } from '../helpers/jest'
import { getDriver, getNeode } from '../db/neo4j'
+import CONFIG from '../config'
const instance = getNeode()
const driver = getDriver()
-let query, authenticatedUser, owner, anotherRegularUser, administrator, variables, moderator
+let query, mutate, variables
+let authenticatedUser, owner, anotherRegularUser, administrator, moderator
describe('authorization', () => {
beforeAll(async () => {
@@ -20,6 +22,7 @@ describe('authorization', () => {
}),
})
query = createTestClient(server).query
+ mutate = createTestClient(server).mutate
})
afterEach(async () => {
@@ -77,7 +80,7 @@ describe('authorization', () => {
describe('access email address', () => {
const userQuery = gql`
- query($name: String) {
+ query ($name: String) {
User(name: $name) {
email
}
@@ -159,5 +162,132 @@ describe('authorization', () => {
})
})
})
+
+ describe('access Signup', () => {
+ const signupMutation = gql`
+ mutation ($email: String!, $inviteCode: String) {
+ Signup(email: $email, inviteCode: $inviteCode) {
+ email
+ }
+ }
+ `
+
+ describe('admin invite only', () => {
+ beforeEach(async () => {
+ variables = {
+ email: 'some@email.org',
+ inviteCode: 'AAAAAA',
+ }
+ CONFIG.INVITE_REGISTRATION = false
+ CONFIG.PUBLIC_REGISTRATION = false
+ await Factory.build('inviteCode', {
+ code: 'AAAAAA',
+ })
+ })
+
+ describe('as user', () => {
+ beforeEach(async () => {
+ authenticatedUser = await anotherRegularUser.toJson()
+ })
+
+ it('denies permission', async () => {
+ await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
+ errors: [{ message: 'Not Authorised!' }],
+ data: { Signup: null },
+ })
+ })
+ })
+
+ describe('as admin', () => {
+ beforeEach(async () => {
+ authenticatedUser = await administrator.toJson()
+ })
+
+ it('returns an email', async () => {
+ await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
+ errors: undefined,
+ data: {
+ Signup: { email: 'some@email.org' },
+ },
+ })
+ })
+ })
+ })
+
+ describe('public registration', () => {
+ beforeEach(async () => {
+ variables = {
+ email: 'some@email.org',
+ inviteCode: 'AAAAAA',
+ }
+ CONFIG.INVITE_REGISTRATION = false
+ CONFIG.PUBLIC_REGISTRATION = true
+ await Factory.build('inviteCode', {
+ code: 'AAAAAA',
+ })
+ })
+
+ describe('as anyone', () => {
+ beforeEach(async () => {
+ authenticatedUser = null
+ })
+
+ it('returns an email', async () => {
+ await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
+ errors: undefined,
+ data: {
+ Signup: { email: 'some@email.org' },
+ },
+ })
+ })
+ })
+ })
+
+ describe('invite registration', () => {
+ beforeEach(async () => {
+ CONFIG.INVITE_REGISTRATION = true
+ CONFIG.PUBLIC_REGISTRATION = false
+ await Factory.build('inviteCode', {
+ code: 'AAAAAA',
+ })
+ })
+
+ describe('as anyone with valid invite code', () => {
+ beforeEach(async () => {
+ variables = {
+ email: 'some@email.org',
+ inviteCode: 'AAAAAA',
+ }
+ authenticatedUser = null
+ })
+
+ it('returns an email', async () => {
+ await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
+ errors: undefined,
+ data: {
+ Signup: { email: 'some@email.org' },
+ },
+ })
+ })
+ })
+
+ describe('as anyone without valid invite', () => {
+ beforeEach(async () => {
+ variables = {
+ email: 'some@email.org',
+ inviteCode: 'no valid invite code',
+ }
+ authenticatedUser = null
+ })
+
+ it('denies permission', async () => {
+ await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
+ errors: [{ message: 'Not Authorised!' }],
+ data: { Signup: null },
+ })
+ })
+ })
+ })
+ })
})
})
diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js
index 97503851a..1f45c29df 100644
--- a/backend/src/middleware/slugifyMiddleware.spec.js
+++ b/backend/src/middleware/slugifyMiddleware.spec.js
@@ -54,7 +54,7 @@ describe('slugifyMiddleware', () => {
describe('CreatePost', () => {
const categoryIds = ['cat9']
const createPostMutation = gql`
- mutation($title: String!, $content: String!, $categoryIds: [ID]!, $slug: String) {
+ mutation ($title: String!, $content: String!, $categoryIds: [ID]!, $slug: String) {
CreatePost(title: $title, content: $content, categoryIds: $categoryIds, slug: $slug) {
slug
}
@@ -164,7 +164,7 @@ describe('slugifyMiddleware', () => {
describe('SignupVerification', () => {
const mutation = gql`
- mutation(
+ mutation (
$password: String!
$email: String!
$name: String!
diff --git a/backend/src/middleware/userInteractions.spec.js b/backend/src/middleware/userInteractions.spec.js
index 77c9fbd1d..27bee87c8 100644
--- a/backend/src/middleware/userInteractions.spec.js
+++ b/backend/src/middleware/userInteractions.spec.js
@@ -10,7 +10,7 @@ const driver = getDriver()
const neode = getNeode()
const postQuery = gql`
- query($id: ID) {
+ query ($id: ID) {
Post(id: $id) {
clickedCount
}
diff --git a/backend/src/middleware/validation/validationMiddleware.spec.js b/backend/src/middleware/validation/validationMiddleware.spec.js
index c3d518256..530a80b1e 100644
--- a/backend/src/middleware/validation/validationMiddleware.spec.js
+++ b/backend/src/middleware/validation/validationMiddleware.spec.js
@@ -17,14 +17,14 @@ let authenticatedUser,
commentingUser
const createCommentMutation = gql`
- mutation($id: ID, $postId: ID!, $content: String!) {
+ mutation ($id: ID, $postId: ID!, $content: String!) {
CreateComment(id: $id, postId: $postId, content: $content) {
id
}
}
`
const updateCommentMutation = gql`
- mutation($content: String!, $id: ID!) {
+ mutation ($content: String!, $id: ID!) {
UpdateComment(content: $content, id: $id) {
id
}
@@ -32,7 +32,7 @@ const updateCommentMutation = gql`
`
const reportMutation = gql`
- mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
+ mutation ($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
fileReport(
resourceId: $resourceId
reasonCategory: $reasonCategory
@@ -43,7 +43,7 @@ const reportMutation = gql`
}
`
const reviewMutation = gql`
- mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
+ mutation ($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
createdAt
updatedAt
@@ -52,7 +52,7 @@ const reviewMutation = gql`
`
const updateUserMutation = gql`
- mutation($id: ID!, $name: String) {
+ mutation ($id: ID!, $name: String) {
UpdateUser(id: $id, name: $name) {
name
}
diff --git a/backend/src/schema/resolvers/comments.spec.js b/backend/src/schema/resolvers/comments.spec.js
index 9f633c8b0..63a5538d0 100644
--- a/backend/src/schema/resolvers/comments.spec.js
+++ b/backend/src/schema/resolvers/comments.spec.js
@@ -36,7 +36,7 @@ afterEach(async () => {
})
const createCommentMutation = gql`
- mutation($id: ID, $postId: ID!, $content: String!) {
+ mutation ($id: ID, $postId: ID!, $content: String!) {
CreateComment(id: $id, postId: $postId, content: $content) {
id
content
@@ -128,7 +128,7 @@ describe('CreateComment', () => {
describe('UpdateComment', () => {
const updateCommentMutation = gql`
- mutation($content: String!, $id: ID!) {
+ mutation ($content: String!, $id: ID!) {
UpdateComment(content: $content, id: $id) {
id
content
@@ -220,7 +220,7 @@ describe('UpdateComment', () => {
describe('DeleteComment', () => {
const deleteCommentMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
DeleteComment(id: $id) {
id
content
diff --git a/backend/src/schema/resolvers/donations.spec.js b/backend/src/schema/resolvers/donations.spec.js
index ea5ee4e09..5bbbd60e0 100644
--- a/backend/src/schema/resolvers/donations.spec.js
+++ b/backend/src/schema/resolvers/donations.spec.js
@@ -9,7 +9,7 @@ const instance = getNeode()
const driver = getDriver()
const updateDonationsMutation = gql`
- mutation($goal: Int, $progress: Int) {
+ mutation ($goal: Int, $progress: Int) {
UpdateDonations(goal: $goal, progress: $progress) {
id
goal
diff --git a/backend/src/schema/resolvers/emails.js b/backend/src/schema/resolvers/emails.js
index 7986f2613..8f6b1c651 100644
--- a/backend/src/schema/resolvers/emails.js
+++ b/backend/src/schema/resolvers/emails.js
@@ -6,6 +6,27 @@ import Validator from 'neode/build/Services/Validator.js'
import normalizeEmail from './helpers/normalizeEmail'
export default {
+ Query: {
+ VerifyNonce: async (_parent, args, context, _resolveInfo) => {
+ const session = context.driver.session()
+ const readTxResultPromise = session.readTransaction(async (txc) => {
+ const result = await txc.run(
+ `
+ MATCH (email:EmailAddress {email: $email, nonce: $nonce})
+ RETURN count(email) > 0 AS result
+ `,
+ { email: args.email, nonce: args.nonce },
+ )
+ return result
+ })
+ try {
+ const txResult = await readTxResultPromise
+ return txResult.records[0].get('result')
+ } finally {
+ session.close()
+ }
+ },
+ },
Mutation: {
AddEmailAddress: async (_parent, args, context, _resolveInfo) => {
let response
diff --git a/backend/src/schema/resolvers/emails.spec.js b/backend/src/schema/resolvers/emails.spec.js
index 94e7ede31..c5596cc27 100644
--- a/backend/src/schema/resolvers/emails.spec.js
+++ b/backend/src/schema/resolvers/emails.spec.js
@@ -6,7 +6,7 @@ import { createTestClient } from 'apollo-server-testing'
const neode = getNeode()
-let mutate
+let mutate, query
let authenticatedUser
let user
let variables
@@ -16,7 +16,8 @@ beforeEach(async () => {
variables = {}
})
-beforeAll(() => {
+beforeAll(async () => {
+ await cleanDatabase()
const { server } = createServer({
context: () => {
return {
@@ -27,6 +28,7 @@ beforeAll(() => {
},
})
mutate = createTestClient(server).mutate
+ query = createTestClient(server).query
})
afterEach(async () => {
@@ -35,7 +37,7 @@ afterEach(async () => {
describe('AddEmailAddress', () => {
const mutation = gql`
- mutation($email: String!) {
+ mutation ($email: String!) {
AddEmailAddress(email: $email) {
email
verifiedAt
@@ -140,7 +142,7 @@ describe('AddEmailAddress', () => {
describe('VerifyEmailAddress', () => {
const mutation = gql`
- mutation($email: String!, $nonce: String!) {
+ mutation ($email: String!, $nonce: String!) {
VerifyEmailAddress(email: $email, nonce: $nonce) {
email
createdAt
@@ -185,7 +187,7 @@ describe('VerifyEmailAddress', () => {
let emailAddress
beforeEach(async () => {
emailAddress = await Factory.build('unverifiedEmailAddress', {
- nonce: 'abcdef',
+ nonce: '12345',
verifiedAt: null,
createdAt: new Date().toISOString(),
email: 'to-be-verified@example.org',
@@ -204,7 +206,7 @@ describe('VerifyEmailAddress', () => {
describe('given valid nonce for `UnverifiedEmailAddress` node', () => {
beforeEach(() => {
- variables = { ...variables, nonce: 'abcdef' }
+ variables = { ...variables, nonce: '12345' }
})
describe('but the address does not belong to the authenticated user', () => {
@@ -295,3 +297,40 @@ describe('VerifyEmailAddress', () => {
})
})
})
+
+describe('VerifyNonce', () => {
+ beforeEach(async () => {
+ await Factory.build('emailAddress', {
+ nonce: '12345',
+ verifiedAt: null,
+ createdAt: new Date().toISOString(),
+ email: 'to-be-verified@example.org',
+ })
+ })
+
+ const verifyNonceQuery = gql`
+ query ($email: String!, $nonce: String!) {
+ VerifyNonce(email: $email, nonce: $nonce)
+ }
+ `
+
+ it('returns true when nonce and email match', async () => {
+ variables = {
+ email: 'to-be-verified@example.org',
+ nonce: '12345',
+ }
+ await expect(query({ query: verifyNonceQuery, variables })).resolves.toMatchObject({
+ data: { VerifyNonce: true },
+ })
+ })
+
+ it('returns false when nonce and email do not match', async () => {
+ variables = {
+ email: 'to-be-verified@example.org',
+ nonce: '---',
+ }
+ await expect(query({ query: verifyNonceQuery, variables })).resolves.toMatchObject({
+ data: { VerifyNonce: false },
+ })
+ })
+})
diff --git a/backend/src/schema/resolvers/embeds.spec.js b/backend/src/schema/resolvers/embeds.spec.js
index 6c034acf7..b7ce7ab63 100644
--- a/backend/src/schema/resolvers/embeds.spec.js
+++ b/backend/src/schema/resolvers/embeds.spec.js
@@ -37,8 +37,7 @@ const babyLovesCatEmbedResponse = new Response(
thumbnail_height: 360,
provider_url: 'https://www.youtube.com/',
thumbnail_width: 480,
- html:
- '',
+ html: '',
thumbnail_url: 'https://i.ytimg.com/vi/qkdXAtO40Fo/hqdefault.jpg',
version: '1.0',
author_name: 'Merkley Family',
@@ -57,7 +56,7 @@ describe('Query', () => {
})
const { query } = createTestClient(server)
const embed = gql`
- query($url: String!) {
+ query ($url: String!) {
embed(url: $url) {
type
title
@@ -204,8 +203,7 @@ Have all the information for the brand in separate config files. Set these defau
video: null,
lang: 'de',
sources: ['resource', 'oembed'],
- html:
- '',
+ html: '',
},
},
errors: undefined,
diff --git a/backend/src/schema/resolvers/follow.spec.js b/backend/src/schema/resolvers/follow.spec.js
index f35795991..6d8db982a 100644
--- a/backend/src/schema/resolvers/follow.spec.js
+++ b/backend/src/schema/resolvers/follow.spec.js
@@ -16,7 +16,7 @@ let user2
let variables
const mutationFollowUser = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
followUser(id: $id) {
name
followedBy {
@@ -29,7 +29,7 @@ const mutationFollowUser = gql`
`
const mutationUnfollowUser = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
unfollowUser(id: $id) {
name
followedBy {
@@ -42,7 +42,7 @@ const mutationUnfollowUser = gql`
`
const userQuery = gql`
- query($id: ID) {
+ query ($id: ID) {
User(id: $id) {
followedBy {
id
diff --git a/backend/src/schema/resolvers/helpers/generateNonce.js b/backend/src/schema/resolvers/helpers/generateNonce.js
index e9b758774..6da40b5c2 100644
--- a/backend/src/schema/resolvers/helpers/generateNonce.js
+++ b/backend/src/schema/resolvers/helpers/generateNonce.js
@@ -1,4 +1,5 @@
-import { v4 as uuid } from 'uuid'
export default function generateNonce() {
- return uuid().substring(0, 6)
+ return Array.from({ length: 5 }, (n = Math.floor(Math.random() * 10)) => {
+ return String.fromCharCode(n + 48)
+ }).join('')
}
diff --git a/backend/src/schema/resolvers/inviteCodes.js b/backend/src/schema/resolvers/inviteCodes.js
index 91148a08d..442ff17b1 100644
--- a/backend/src/schema/resolvers/inviteCodes.js
+++ b/backend/src/schema/resolvers/inviteCodes.js
@@ -1,5 +1,6 @@
import generateInviteCode from './helpers/generateInviteCode'
import Resolver from './helpers/Resolver'
+import { validateInviteCode } from './transactions/inviteCodes'
const uniqueInviteCode = async (session, code) => {
return session.readTransaction(async (txc) => {
@@ -12,6 +13,52 @@ const uniqueInviteCode = async (session, code) => {
export default {
Query: {
+ getInviteCode: async (_parent, args, context, _resolveInfo) => {
+ const {
+ user: { id: userId },
+ } = context
+ const session = context.driver.session()
+ const readTxResultPromise = session.readTransaction(async (txc) => {
+ const result = await txc.run(
+ `MATCH (user:User {id: $userId})-[:GENERATED]->(ic:InviteCode)
+ WHERE ic.expiresAt IS NULL
+ OR datetime(ic.expiresAt) >= datetime()
+ RETURN properties(ic) AS inviteCodes`,
+ {
+ userId,
+ },
+ )
+ return result.records.map((record) => record.get('inviteCodes'))
+ })
+ try {
+ const inviteCode = await readTxResultPromise
+ if (inviteCode && inviteCode.length > 0) return inviteCode[0]
+ let code = generateInviteCode()
+ while (!(await uniqueInviteCode(session, code))) {
+ code = generateInviteCode()
+ }
+ const writeTxResultPromise = session.writeTransaction(async (txc) => {
+ const result = await txc.run(
+ `MATCH (user:User {id: $userId})
+ MERGE (user)-[:GENERATED]->(ic:InviteCode { code: $code })
+ ON CREATE SET
+ ic.createdAt = toString(datetime()),
+ ic.expiresAt = $expiresAt
+ RETURN ic AS inviteCode`,
+ {
+ userId,
+ code,
+ expiresAt: null,
+ },
+ )
+ return result.records.map((record) => record.get('inviteCode').properties)
+ })
+ const txResult = await writeTxResultPromise
+ return txResult[0]
+ } finally {
+ session.close()
+ }
+ },
MyInviteCodes: async (_parent, args, context, _resolveInfo) => {
const {
user: { id: userId },
@@ -36,28 +83,9 @@ export default {
},
isValidInviteCode: async (_parent, args, context, _resolveInfo) => {
const { code } = args
- if (!code) return false
const session = context.driver.session()
- const readTxResultPromise = session.readTransaction(async (txc) => {
- const result = await txc.run(
- `MATCH (ic:InviteCode { code: toUpper($code) })
- RETURN
- CASE
- WHEN ic.expiresAt IS NULL THEN true
- WHEN datetime(ic.expiresAt) >= datetime() THEN true
- ELSE false END AS result`,
- {
- code,
- },
- )
- return result.records.map((record) => record.get('result'))
- })
- try {
- const txResult = await readTxResultPromise
- return !!txResult[0]
- } finally {
- session.close()
- }
+ if (!code) return false
+ return validateInviteCode(session, code)
},
},
Mutation: {
diff --git a/backend/src/schema/resolvers/inviteCodes.spec.js b/backend/src/schema/resolvers/inviteCodes.spec.js
index 19f021437..940aa8403 100644
--- a/backend/src/schema/resolvers/inviteCodes.spec.js
+++ b/backend/src/schema/resolvers/inviteCodes.spec.js
@@ -11,7 +11,7 @@ let mutate
const driver = getDriver()
const generateInviteCodeMutation = gql`
- mutation($expiresAt: String = null) {
+ mutation ($expiresAt: String = null) {
GenerateInviteCode(expiresAt: $expiresAt) {
code
createdAt
@@ -31,7 +31,7 @@ const myInviteCodesQuery = gql`
`
const isValidInviteCodeQuery = gql`
- query($code: ID!) {
+ query ($code: ID!) {
isValidInviteCode(code: $code)
}
`
diff --git a/backend/src/schema/resolvers/locations.spec.js b/backend/src/schema/resolvers/locations.spec.js
index 34d0f2e9d..fa562cbfd 100644
--- a/backend/src/schema/resolvers/locations.spec.js
+++ b/backend/src/schema/resolvers/locations.spec.js
@@ -31,7 +31,7 @@ describe('resolvers', () => {
describe('custom mutation, not handled by neo4j-graphql-js', () => {
let variables
const updateUserMutation = gql`
- mutation($id: ID!, $name: String) {
+ mutation ($id: ID!, $name: String) {
UpdateUser(id: $id, name: $name) {
name
location {
diff --git a/backend/src/schema/resolvers/moderation.spec.js b/backend/src/schema/resolvers/moderation.spec.js
index b62d35ee8..a579c62da 100644
--- a/backend/src/schema/resolvers/moderation.spec.js
+++ b/backend/src/schema/resolvers/moderation.spec.js
@@ -16,7 +16,7 @@ let mutate,
closeReportVariables
const reviewMutation = gql`
- mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
+ mutation ($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
createdAt
updatedAt
diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js
index 9d7795dd4..f600e7d1c 100644
--- a/backend/src/schema/resolvers/notifications.spec.js
+++ b/backend/src/schema/resolvers/notifications.spec.js
@@ -139,7 +139,7 @@ describe('given some notifications', () => {
describe('notifications', () => {
const notificationQuery = gql`
- query($read: Boolean, $orderBy: NotificationOrdering) {
+ query ($read: Boolean, $orderBy: NotificationOrdering) {
notifications(read: $read, orderBy: $orderBy) {
from {
__typename
@@ -249,7 +249,7 @@ describe('given some notifications', () => {
const deletePostAction = async () => {
authenticatedUser = await author.toJson()
const deletePostMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
DeletePost(id: $id) {
id
deleted
@@ -284,7 +284,7 @@ describe('given some notifications', () => {
describe('markAsRead', () => {
const markAsReadMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
markAsRead(id: $id) {
from {
__typename
diff --git a/backend/src/schema/resolvers/passwordReset.spec.js b/backend/src/schema/resolvers/passwordReset.spec.js
index fd1395c57..40a18f5d9 100644
--- a/backend/src/schema/resolvers/passwordReset.spec.js
+++ b/backend/src/schema/resolvers/passwordReset.spec.js
@@ -55,7 +55,7 @@ describe('passwordReset', () => {
describe('requestPasswordReset', () => {
const mutation = gql`
- mutation($email: String!) {
+ mutation ($email: String!) {
requestPasswordReset(email: $email)
}
`
@@ -116,7 +116,7 @@ describe('resetPassword', () => {
}
const mutation = gql`
- mutation($nonce: String!, $email: String!, $newPassword: String!) {
+ mutation ($nonce: String!, $email: String!, $newPassword: String!) {
resetPassword(nonce: $nonce, email: $email, newPassword: $newPassword)
}
`
@@ -196,7 +196,7 @@ describe('resetPassword', () => {
it('updates password of the user', async () => {
await mutate({ mutation, variables })
const checkLoginMutation = gql`
- mutation($email: String!, $password: String!) {
+ mutation ($email: String!, $password: String!) {
login(email: $email, password: $password)
}
`
diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js
index b7b77bbd5..d199b6f09 100644
--- a/backend/src/schema/resolvers/posts.js
+++ b/backend/src/schema/resolvers/posts.js
@@ -348,7 +348,7 @@ export default {
undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'],
hasMany: {
tags: '-[:TAGGED]->(related:Tag)',
- categories: '-[:CATEGORIZED]->(related:Category)',
+ // categories: '-[:CATEGORIZED]->(related:Category)',
comments: '<-[:COMMENTS]-(related:Comment)',
shoutedBy: '<-[:SHOUTED]-(related:User)',
emotions: '<-[related:EMOTED]',
diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js
index f0c57b8fb..20dbecfb6 100644
--- a/backend/src/schema/resolvers/posts.spec.js
+++ b/backend/src/schema/resolvers/posts.spec.js
@@ -16,7 +16,7 @@ const categoryIds = ['cat9', 'cat4', 'cat15']
let variables
const createPostMutation = gql`
- mutation($id: ID, $title: String!, $content: String!, $language: String, $categoryIds: [ID]) {
+ mutation ($id: ID, $title: String!, $content: String!, $language: String, $categoryIds: [ID]) {
CreatePost(
id: $id
title: $title
@@ -147,7 +147,7 @@ describe('Post', () => {
})
})
- it('by categories', async () => {
+ /* it('by categories', async () => {
const postQueryFilteredByCategories = gql`
query Post($filter: _PostFilter) {
Post(filter: $filter) {
@@ -172,7 +172,7 @@ describe('Post', () => {
await expect(
query({ query: postQueryFilteredByCategories, variables }),
).resolves.toMatchObject(expected)
- })
+ }) */
describe('by emotions', () => {
const postQueryFilteredByEmotions = gql`
@@ -323,14 +323,8 @@ describe('CreatePost', () => {
describe('UpdatePost', () => {
let author, newlyCreatedPost
const updatePostMutation = gql`
- mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID], $image: ImageInput) {
- UpdatePost(
- id: $id
- title: $title
- content: $content
- categoryIds: $categoryIds
- image: $image
- ) {
+ mutation ($id: ID!, $title: String!, $content: String!, $image: ImageInput) {
+ UpdatePost(id: $id, title: $title, content: $content, image: $image) {
id
title
content
@@ -338,9 +332,6 @@ describe('UpdatePost', () => {
name
slug
}
- categories {
- id
- }
createdAt
updatedAt
}
@@ -428,7 +419,7 @@ describe('UpdatePost', () => {
expect(newlyCreatedPost.updatedAt).not.toEqual(UpdatePost.updatedAt)
})
- describe('no new category ids provided for update', () => {
+ /* describe('no new category ids provided for update', () => {
it('resolves and keeps current categories', async () => {
const expected = {
data: {
@@ -443,9 +434,9 @@ describe('UpdatePost', () => {
expected,
)
})
- })
+ }) */
- describe('given category ids', () => {
+ /* describe('given category ids', () => {
beforeEach(() => {
variables = { ...variables, categoryIds: ['cat27'] }
})
@@ -464,7 +455,7 @@ describe('UpdatePost', () => {
expected,
)
})
- })
+ }) */
describe('params.image', () => {
describe('is object', () => {
@@ -506,7 +497,7 @@ describe('UpdatePost', () => {
describe('pin posts', () => {
let author
const pinPostMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
pinPost(id: $id) {
id
title
@@ -782,7 +773,7 @@ describe('pin posts', () => {
it('pinned post appear first even when created before other posts', async () => {
const postOrderingQuery = gql`
- query($orderBy: [_PostOrdering]) {
+ query ($orderBy: [_PostOrdering]) {
Post(orderBy: $orderBy) {
id
pinned
@@ -825,7 +816,7 @@ describe('pin posts', () => {
describe('unpin posts', () => {
let pinnedPost
const unpinPostMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
unpinPost(id: $id) {
id
title
@@ -937,7 +928,7 @@ describe('unpin posts', () => {
describe('DeletePost', () => {
let author
const deletePostMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
DeletePost(id: $id) {
id
deleted
@@ -1061,14 +1052,14 @@ describe('DeletePost', () => {
describe('emotions', () => {
let author, postToEmote
const PostsEmotionsCountQuery = gql`
- query($id: ID!) {
+ query ($id: ID!) {
Post(id: $id) {
emotionsCount
}
}
`
const PostsEmotionsQuery = gql`
- query($id: ID!) {
+ query ($id: ID!) {
Post(id: $id) {
emotions {
emotion
@@ -1102,7 +1093,7 @@ describe('emotions', () => {
describe('AddPostEmotions', () => {
const addPostEmotionsMutation = gql`
- mutation($to: _PostInput!, $data: _EMOTEDInput!) {
+ mutation ($to: _PostInput!, $data: _EMOTEDInput!) {
AddPostEmotions(to: $to, data: $data) {
from {
id
@@ -1219,7 +1210,7 @@ describe('emotions', () => {
describe('RemovePostEmotions', () => {
let removePostEmotionsVariables, postsEmotionsQueryVariables
const removePostEmotionsMutation = gql`
- mutation($to: _PostInput!, $data: _EMOTEDInput!) {
+ mutation ($to: _PostInput!, $data: _EMOTEDInput!) {
RemovePostEmotions(to: $to, data: $data) {
from {
id
@@ -1318,13 +1309,13 @@ describe('emotions', () => {
let PostsEmotionsByCurrentUserVariables
const PostsEmotionsCountByEmotionQuery = gql`
- query($postId: ID!, $data: _EMOTEDInput!) {
+ query ($postId: ID!, $data: _EMOTEDInput!) {
PostsEmotionsCountByEmotion(postId: $postId, data: $data)
}
`
const PostsEmotionsByCurrentUserQuery = gql`
- query($postId: ID!) {
+ query ($postId: ID!) {
PostsEmotionsByCurrentUser(postId: $postId)
}
`
diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js
index f1c43be21..2796028fe 100644
--- a/backend/src/schema/resolvers/registration.js
+++ b/backend/src/schema/resolvers/registration.js
@@ -29,34 +29,22 @@ export default {
}
args.termsAndConditionsAgreedAt = new Date().toISOString()
- let { nonce, email } = args
+ let { nonce, email, inviteCode } = args
email = normalizeEmail(email)
delete args.nonce
delete args.email
+ delete args.inviteCode
args = encryptPassword(args)
const { driver } = context
const session = driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
- const createUserTransactionResponse = await transaction.run(
- `
- MATCH(email:EmailAddress {nonce: $nonce, email: $email})
- WHERE NOT (email)-[:BELONGS_TO]->()
- CREATE (user:User)
- MERGE(user)-[:PRIMARY_EMAIL]->(email)
- MERGE(user)<-[:BELONGS_TO]-(email)
- SET user += $args
- SET user.id = randomUUID()
- SET user.role = 'user'
- SET user.createdAt = toString(datetime())
- SET user.updatedAt = toString(datetime())
- SET user.allowEmbedIframes = FALSE
- SET user.showShoutsPublicly = FALSE
- SET email.verifiedAt = toString(datetime())
- RETURN user {.*}
- `,
- { args, nonce, email },
- )
+ const createUserTransactionResponse = await transaction.run(signupCypher(inviteCode), {
+ args,
+ nonce,
+ email,
+ inviteCode,
+ })
const [user] = createUserTransactionResponse.records.map((record) => record.get('user'))
if (!user) throw new UserInputError('Invalid email or nonce')
return user
@@ -74,3 +62,39 @@ export default {
},
},
}
+
+const signupCypher = (inviteCode) => {
+ let optionalMatch = ''
+ let optionalMerge = ''
+ if (inviteCode) {
+ optionalMatch = `
+ OPTIONAL MATCH
+ (inviteCode:InviteCode {code: $inviteCode})<-[:GENERATED]-(host:User)
+ `
+ optionalMerge = `
+ MERGE(user)-[:REDEEMED { createdAt: toString(datetime()) }]->(inviteCode)
+ MERGE(host)-[:INVITED { createdAt: toString(datetime()) }]->(user)
+ MERGE(user)-[:FOLLOWS { createdAt: toString(datetime()) }]->(host)
+ MERGE(host)-[:FOLLOWS { createdAt: toString(datetime()) }]->(user)
+ `
+ }
+ const cypher = `
+ MATCH(email:EmailAddress {nonce: $nonce, email: $email})
+ WHERE NOT (email)-[:BELONGS_TO]->()
+ ${optionalMatch}
+ CREATE (user:User)
+ MERGE(user)-[:PRIMARY_EMAIL]->(email)
+ MERGE(user)<-[:BELONGS_TO]-(email)
+ ${optionalMerge}
+ SET user += $args
+ SET user.id = randomUUID()
+ SET user.role = 'user'
+ SET user.createdAt = toString(datetime())
+ SET user.updatedAt = toString(datetime())
+ SET user.allowEmbedIframes = FALSE
+ SET user.showShoutsPublicly = FALSE
+ SET email.verifiedAt = toString(datetime())
+ RETURN user {.*}
+ `
+ return cypher
+}
diff --git a/backend/src/schema/resolvers/registration.spec.js b/backend/src/schema/resolvers/registration.spec.js
index 63dc35519..25e8fd1c8 100644
--- a/backend/src/schema/resolvers/registration.spec.js
+++ b/backend/src/schema/resolvers/registration.spec.js
@@ -3,6 +3,7 @@ import { gql } from '../../helpers/jest'
import { getDriver, getNeode } from '../../db/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
+import CONFIG from '../../config'
const neode = getNeode()
@@ -15,7 +16,8 @@ beforeEach(async () => {
variables = {}
})
-beforeAll(() => {
+beforeAll(async () => {
+ await cleanDatabase()
const { server } = createServer({
context: () => {
return {
@@ -34,8 +36,8 @@ afterEach(async () => {
describe('Signup', () => {
const mutation = gql`
- mutation($email: String!) {
- Signup(email: $email) {
+ mutation ($email: String!, $inviteCode: String) {
+ Signup(email: $email, inviteCode: $inviteCode) {
email
}
}
@@ -50,6 +52,8 @@ describe('Signup', () => {
})
it('throws AuthorizationError', async () => {
+ CONFIG.INVITE_REGISTRATION = false
+ CONFIG.PUBLIC_REGISTRATION = false
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }],
})
@@ -141,7 +145,7 @@ describe('Signup', () => {
describe('SignupVerification', () => {
const mutation = gql`
- mutation(
+ mutation (
$name: String!
$password: String!
$email: String!
diff --git a/backend/src/schema/resolvers/reports.spec.js b/backend/src/schema/resolvers/reports.spec.js
index 2ecccfc23..1208db7fe 100644
--- a/backend/src/schema/resolvers/reports.spec.js
+++ b/backend/src/schema/resolvers/reports.spec.js
@@ -11,7 +11,7 @@ describe('file a report on a resource', () => {
let authenticatedUser, currentUser, mutate, query, moderator, abusiveUser, otherReportingUser
const categoryIds = ['cat9']
const fileReportMutation = gql`
- mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
+ mutation ($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
fileReport(
resourceId: $resourceId
reasonCategory: $reasonCategory
@@ -42,7 +42,7 @@ describe('file a report on a resource', () => {
reasonDescription: 'Violates code of conduct !!!',
}
const reportsQuery = gql`
- query($closed: Boolean) {
+ query ($closed: Boolean) {
reports(orderBy: createdAt_desc, closed: $closed) {
id
createdAt
@@ -74,7 +74,7 @@ describe('file a report on a resource', () => {
}
`
const reviewMutation = gql`
- mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
+ mutation ($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
createdAt
resource {
diff --git a/backend/src/schema/resolvers/rewards.spec.js b/backend/src/schema/resolvers/rewards.spec.js
index a20472243..0f76e1ed1 100644
--- a/backend/src/schema/resolvers/rewards.spec.js
+++ b/backend/src/schema/resolvers/rewards.spec.js
@@ -16,6 +16,7 @@ describe('rewards', () => {
}
beforeAll(async () => {
+ await cleanDatabase()
const { server } = createServer({
context: () => {
return {
@@ -75,7 +76,7 @@ describe('rewards', () => {
describe('reward', () => {
const rewardMutation = gql`
- mutation($from: ID!, $to: ID!) {
+ mutation ($from: ID!, $to: ID!) {
reward(badgeKey: $from, userId: $to) {
id
badges {
@@ -265,7 +266,7 @@ describe('rewards', () => {
}
const unrewardMutation = gql`
- mutation($from: ID!, $to: ID!) {
+ mutation ($from: ID!, $to: ID!) {
unreward(badgeKey: $from, userId: $to) {
id
badges {
diff --git a/backend/src/schema/resolvers/searches.spec.js b/backend/src/schema/resolvers/searches.spec.js
index a859bf296..ceb63304e 100644
--- a/backend/src/schema/resolvers/searches.spec.js
+++ b/backend/src/schema/resolvers/searches.spec.js
@@ -28,7 +28,7 @@ afterAll(async () => {
})
const searchQuery = gql`
- query($query: String!) {
+ query ($query: String!) {
searchResults(query: $query, limit: 5) {
__typename
... on Post {
@@ -49,7 +49,7 @@ const searchQuery = gql`
`
const searchPostQuery = gql`
- query($query: String!, $firstPosts: Int, $postsOffset: Int) {
+ query ($query: String!, $firstPosts: Int, $postsOffset: Int) {
searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) {
postCount
posts {
diff --git a/backend/src/schema/resolvers/shout.spec.js b/backend/src/schema/resolvers/shout.spec.js
index 574907180..383bd83f0 100644
--- a/backend/src/schema/resolvers/shout.spec.js
+++ b/backend/src/schema/resolvers/shout.spec.js
@@ -9,17 +9,17 @@ const instance = getNeode()
const driver = getDriver()
const mutationShoutPost = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
shout(id: $id, type: Post)
}
`
const mutationUnshoutPost = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
unshout(id: $id, type: Post)
}
`
const queryPost = gql`
- query($id: ID!) {
+ query ($id: ID!) {
Post(id: $id) {
id
shoutedBy {
diff --git a/backend/src/schema/resolvers/socialMedia.spec.js b/backend/src/schema/resolvers/socialMedia.spec.js
index 898174199..cff20185b 100644
--- a/backend/src/schema/resolvers/socialMedia.spec.js
+++ b/backend/src/schema/resolvers/socialMedia.spec.js
@@ -70,7 +70,7 @@ describe('SocialMedia', () => {
beforeEach(() => {
mutation = gql`
- mutation($url: String!) {
+ mutation ($url: String!) {
CreateSocialMedia(url: $url) {
id
url
@@ -131,7 +131,7 @@ describe('SocialMedia', () => {
describe('ownedBy', () => {
beforeEach(() => {
mutation = gql`
- mutation($url: String!) {
+ mutation ($url: String!) {
CreateSocialMedia(url: $url) {
url
ownedBy {
@@ -162,7 +162,7 @@ describe('SocialMedia', () => {
const socialMedia = await setUpSocialMedia()
mutation = gql`
- mutation($id: ID!, $url: String!) {
+ mutation ($id: ID!, $url: String!) {
UpdateSocialMedia(id: $id, url: $url) {
id
url
@@ -225,7 +225,7 @@ describe('SocialMedia', () => {
const socialMedia = await setUpSocialMedia()
mutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
DeleteSocialMedia(id: $id) {
id
url
diff --git a/backend/src/schema/resolvers/transactions/inviteCodes.js b/backend/src/schema/resolvers/transactions/inviteCodes.js
new file mode 100644
index 000000000..554b15f86
--- /dev/null
+++ b/backend/src/schema/resolvers/transactions/inviteCodes.js
@@ -0,0 +1,22 @@
+export async function validateInviteCode(session, inviteCode) {
+ const readTxResultPromise = session.readTransaction(async (txc) => {
+ const result = await txc.run(
+ `MATCH (ic:InviteCode { code: toUpper($inviteCode) })
+ RETURN
+ CASE
+ WHEN ic.expiresAt IS NULL THEN true
+ WHEN datetime(ic.expiresAt) >= datetime() THEN true
+ ELSE false END AS result`,
+ {
+ inviteCode,
+ },
+ )
+ return result.records.map((record) => record.get('result'))
+ })
+ try {
+ const txResult = await readTxResultPromise
+ return !!txResult[0]
+ } finally {
+ session.close()
+ }
+}
diff --git a/backend/src/schema/resolvers/userData.spec.js b/backend/src/schema/resolvers/userData.spec.js
index 972248d50..0a4e54295 100644
--- a/backend/src/schema/resolvers/userData.spec.js
+++ b/backend/src/schema/resolvers/userData.spec.js
@@ -39,7 +39,7 @@ afterAll(async () => {
})
const userDataQuery = gql`
- query($id: ID!) {
+ query ($id: ID!) {
userData(id: $id) {
user {
id
diff --git a/backend/src/schema/resolvers/user_management.spec.js b/backend/src/schema/resolvers/user_management.spec.js
index b434ea628..57fe4a7f4 100644
--- a/backend/src/schema/resolvers/user_management.spec.js
+++ b/backend/src/schema/resolvers/user_management.spec.js
@@ -171,7 +171,7 @@ describe('currentUser', () => {
describe('login', () => {
const loginMutation = gql`
- mutation($email: String!, $password: String!) {
+ mutation ($email: String!, $password: String!) {
login(email: $email, password: $password)
}
`
@@ -287,7 +287,7 @@ describe('login', () => {
describe('change password', () => {
const changePasswordMutation = gql`
- mutation($oldPassword: String!, $newPassword: String!) {
+ mutation ($oldPassword: String!, $newPassword: String!) {
changePassword(oldPassword: $oldPassword, newPassword: $newPassword)
}
`
diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js
index e5e818040..6fd893240 100644
--- a/backend/src/schema/resolvers/users.spec.js
+++ b/backend/src/schema/resolvers/users.spec.js
@@ -17,7 +17,7 @@ const driver = getDriver()
const neode = getNeode()
const deleteUserMutation = gql`
- mutation($id: ID!, $resource: [Deletable]) {
+ mutation ($id: ID!, $resource: [Deletable]) {
DeleteUser(id: $id, resource: $resource) {
id
name
@@ -46,7 +46,7 @@ const deleteUserMutation = gql`
`
const switchUserRoleMutation = gql`
- mutation($role: UserGroup!, $id: ID!) {
+ mutation ($role: UserGroup!, $id: ID!) {
switchUserRole(role: $role, id: $id) {
name
role
@@ -81,7 +81,7 @@ describe('User', () => {
beforeEach(async () => {
userQuery = gql`
- query($email: String) {
+ query ($email: String) {
User(email: $email) {
name
}
@@ -144,7 +144,7 @@ describe('UpdateUser', () => {
beforeEach(async () => {
updateUserMutation = gql`
- mutation(
+ mutation (
$id: ID!
$name: String
$termsAndConditionsAgreedVersion: String
diff --git a/backend/src/schema/resolvers/users/location.spec.js b/backend/src/schema/resolvers/users/location.spec.js
index 41b784249..c966fe720 100644
--- a/backend/src/schema/resolvers/users/location.spec.js
+++ b/backend/src/schema/resolvers/users/location.spec.js
@@ -9,7 +9,7 @@ const driver = getDriver()
let authenticatedUser, mutate, query, variables
const updateUserMutation = gql`
- mutation($id: ID!, $name: String!, $locationName: String) {
+ mutation ($id: ID!, $name: String!, $locationName: String) {
UpdateUser(id: $id, name: $name, locationName: $locationName) {
locationName
}
@@ -17,7 +17,7 @@ const updateUserMutation = gql`
`
const queryLocations = gql`
- query($place: String!, $lang: String!) {
+ query ($place: String!, $lang: String!) {
queryLocations(place: $place, lang: $lang) {
place_name
id
@@ -114,10 +114,22 @@ describe('Location Service', () => {
const result = await query({ query: queryLocations, variables })
expect(result.data.queryLocations).toEqual([
{ id: 'place.14094307404564380', place_name: 'Berlin, Germany' },
- { id: 'place.15095411613564380', place_name: 'Berlin, Maryland, United States' },
- { id: 'place.5225018734564380', place_name: 'Berlin, Connecticut, United States' },
- { id: 'place.16922023226564380', place_name: 'Berlin, New Jersey, United States' },
- { id: 'place.4035845612564380', place_name: 'Berlin Township, New Jersey, United States' },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin, Maryland, United States',
+ },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin, Connecticut, United States',
+ },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin, New Jersey, United States',
+ },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin Township, New Jersey, United States',
+ },
])
})
@@ -128,11 +140,23 @@ describe('Location Service', () => {
}
const result = await query({ query: queryLocations, variables })
expect(result.data.queryLocations).toEqual([
- { id: 'place.14094307404564380', place_name: 'Berlin, Deutschland' },
- { id: 'place.15095411613564380', place_name: 'Berlin, Maryland, Vereinigte Staaten' },
- { id: 'place.16922023226564380', place_name: 'Berlin, New Jersey, Vereinigte Staaten' },
- { id: 'place.10735893248465990', place_name: 'Berlin Heights, Ohio, Vereinigte Staaten' },
- { id: 'place.1165756679564380', place_name: 'Berlin, Massachusetts, Vereinigte Staaten' },
+ { id: expect.stringMatching(/^place\.[0-9]+$/), place_name: 'Berlin, Deutschland' },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin, Maryland, Vereinigte Staaten',
+ },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin, New Jersey, Vereinigte Staaten',
+ },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin Heights, Ohio, Vereinigte Staaten',
+ },
+ {
+ id: expect.stringMatching(/^place\.[0-9]+$/),
+ place_name: 'Berlin, Massachusetts, Vereinigte Staaten',
+ },
])
})
diff --git a/backend/src/schema/resolvers/users/mutedUsers.spec.js b/backend/src/schema/resolvers/users/mutedUsers.spec.js
index 345b435f5..c86cf2eb1 100644
--- a/backend/src/schema/resolvers/users/mutedUsers.spec.js
+++ b/backend/src/schema/resolvers/users/mutedUsers.spec.js
@@ -93,7 +93,7 @@ describe('muteUser', () => {
muteAction = (variables) => {
const { mutate } = createTestClient(server)
const muteUserMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
muteUser(id: $id) {
id
name
@@ -310,7 +310,7 @@ describe('unmuteUser', () => {
unmuteAction = (variables) => {
const { mutate } = createTestClient(server)
const unmuteUserMutation = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
unmuteUser(id: $id) {
id
name
diff --git a/backend/src/schema/resolvers/viewedTeaserCount.spec.js b/backend/src/schema/resolvers/viewedTeaserCount.spec.js
index c81e272ec..ceb95c0ed 100644
--- a/backend/src/schema/resolvers/viewedTeaserCount.spec.js
+++ b/backend/src/schema/resolvers/viewedTeaserCount.spec.js
@@ -32,7 +32,7 @@ afterAll(async () => {
describe('count post teaser views', () => {
let aUser, bUser
const markTeaserAsViewed = gql`
- mutation($id: ID!) {
+ mutation ($id: ID!) {
markTeaserAsViewed(id: $id) {
id
viewedTeaserCount
diff --git a/backend/src/schema/types/type/EmailAddress.gql b/backend/src/schema/types/type/EmailAddress.gql
index e09ec9e63..b2e65eafa 100644
--- a/backend/src/schema/types/type/EmailAddress.gql
+++ b/backend/src/schema/types/type/EmailAddress.gql
@@ -4,12 +4,16 @@ type EmailAddress {
createdAt: String
}
+type Query {
+ VerifyNonce(email: String!, nonce: String!): Boolean!
+}
+
type Mutation {
- Signup(email: String!): EmailAddress
- SignupByInvitation(email: String!, token: String!): EmailAddress
+ Signup(email: String!, inviteCode: String = null): EmailAddress
SignupVerification(
nonce: String!
email: String!
+ inviteCode: String = null
name: String!
password: String!
slug: String
diff --git a/backend/src/schema/types/type/InviteCode.gql b/backend/src/schema/types/type/InviteCode.gql
index 8ad7851a2..3293c735b 100644
--- a/backend/src/schema/types/type/InviteCode.gql
+++ b/backend/src/schema/types/type/InviteCode.gql
@@ -14,4 +14,5 @@ type Mutation {
type Query {
MyInviteCodes: [InviteCode]
isValidInviteCode(code: ID!): Boolean
+ getInviteCode: InviteCode
}
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 7d6558da0..aeb0e29ef 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -910,6 +910,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.9.2":
+ version "7.12.5"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
+ integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
@@ -956,32 +963,32 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
-"@graphql-toolkit/common@0.9.12":
- version "0.9.12"
- resolved "https://registry.yarnpkg.com/@graphql-toolkit/common/-/common-0.9.12.tgz#a3bf91d00130100cfa0a72be718b16cc1011ab45"
- integrity sha512-NjBVxeM1GB0bldiBm4UQoxKtbu/hjRfcqDEt1JPia+uXgFde3qTxtF5IZkworRFNCp+8KPSVrwcreQHSfFW63w==
+"@graphql-toolkit/common@0.10.4":
+ version "0.10.4"
+ resolved "https://registry.yarnpkg.com/@graphql-toolkit/common/-/common-0.10.4.tgz#7785f2a3f14559d0778859c49f4442078c196695"
+ integrity sha512-HQ3HaxCqX+UE8y/0h7LMDBBGSIKJxY/gaQesaksvE2Y+N4NpSWdiW6HpOcgXfC2HGf9yM0hEdsERzzL8z3mbHQ==
dependencies:
aggregate-error "3.0.1"
camel-case "4.1.1"
- graphql-tools-fork "9.0.1"
+ graphql-tools "5.0.0"
lodash "4.17.15"
-"@graphql-toolkit/file-loading@0.9.12":
- version "0.9.12"
- resolved "https://registry.yarnpkg.com/@graphql-toolkit/file-loading/-/file-loading-0.9.12.tgz#f7d5d7e042df59c4e6c8fe3417c67dd1cddf9043"
- integrity sha512-xEmKkbWI5FXQARTN3dbPzshwcmduOJUvx1QGsfcuryVDdQJZynUEaPF7IxYaFpvQF7vwxGIRdff/jC/a5ypDrA==
+"@graphql-toolkit/file-loading@0.10.4":
+ version "0.10.4"
+ resolved "https://registry.yarnpkg.com/@graphql-toolkit/file-loading/-/file-loading-0.10.4.tgz#50e8933e44b17853544c1fe63350df93f33a5e80"
+ integrity sha512-oUmy/sO3BJfax85pVKI7FZ6TWrViNuWXoJkRM293YV9bKGuYU9TgqZoHyM+oEqWO5ruXCL/nCdw3cIBau+rSNA==
dependencies:
globby "11.0.0"
unixify "1.0.0"
-"@graphql-toolkit/schema-merging@0.9.12":
- version "0.9.12"
- resolved "https://registry.yarnpkg.com/@graphql-toolkit/schema-merging/-/schema-merging-0.9.12.tgz#f058a7d256a4ed0e61e9874f90ae03326b81668e"
- integrity sha512-ciqxLeMw7KPbJcq/xgnPbGyJGRO6bO1zQcdWCUSssyw8VDRHj5PFqEOAzT88eZQkEtg3qLN/wQEypeyFyNTHzw==
+"@graphql-toolkit/schema-merging@0.10.4":
+ version "0.10.4"
+ resolved "https://registry.yarnpkg.com/@graphql-toolkit/schema-merging/-/schema-merging-0.10.4.tgz#2428590a531a33e9fe03be27cce9030f1c4c044b"
+ integrity sha512-naL6reYBuILLMrkMfKz0lOLL0kl6gGYnaaywnO/Dgp9F4NeAxDdAs5CV6Fy9NO5OzePFP58Dnc4sh2RyYrrFJg==
dependencies:
- "@graphql-toolkit/common" "0.9.12"
+ "@graphql-toolkit/common" "0.10.4"
deepmerge "4.2.2"
- graphql-tools-fork "9.0.1"
+ graphql-tools "5.0.0"
tslib "1.11.1"
"@hapi/address@2.x.x":
@@ -2036,7 +2043,7 @@ apollo-link-context@~1.0.20:
apollo-link "^1.2.14"
tslib "^1.9.3"
-apollo-link-http-common@^0.2.15, apollo-link-http-common@^0.2.16:
+apollo-link-http-common@^0.2.14, apollo-link-http-common@^0.2.16:
version "0.2.16"
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz#756749dafc732792c8ca0923f9a40564b7c59ecc"
integrity sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg==
@@ -2054,7 +2061,7 @@ apollo-link-http@~1.5.17:
apollo-link-http-common "^0.2.16"
tslib "^1.9.3"
-apollo-link@^1.0.0, apollo-link@^1.2.13, apollo-link@^1.2.14:
+apollo-link@^1.0.0, apollo-link@^1.2.12, apollo-link@^1.2.14:
version "1.2.14"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9"
integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==
@@ -2211,6 +2218,16 @@ apollo-tracing@^0.12.0:
apollo-server-env "^2.4.5"
apollo-server-plugin-base "^0.10.2"
+apollo-upload-client@^13.0.0:
+ version "13.0.0"
+ resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-13.0.0.tgz#146d1ddd85d711fcac8ca97a72d3ca6787f2b71b"
+ integrity sha512-lJ9/bk1BH1lD15WhWRha2J3+LrXrPIX5LP5EwiOUHv8PCORp4EUrcujrA3rI5hZeZygrTX8bshcuMdpqpSrvtA==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+ apollo-link "^1.2.12"
+ apollo-link-http-common "^0.2.14"
+ extract-files "^8.0.0"
+
apollo-utilities@1.3.3, apollo-utilities@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.3.tgz#f1854715a7be80cd810bc3ac95df085815c0787c"
@@ -3392,10 +3409,10 @@ data-urls@^1.1.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
-date-fns@2.11.1:
- version "2.11.1"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.11.1.tgz#197b8be1bbf5c5e6fe8bea817f0fe111820e7a12"
- integrity sha512-3RdUoinZ43URd2MJcquzBbDQo+J87cSzB8NkXdZiN5ia1UNyep0oCyitfiL88+R7clGTeq/RniXAc16gWyAu1w==
+date-fns@2.22.1:
+ version "2.22.1"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
+ integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
dateformat@^3.0.3:
version "3.0.3"
@@ -4299,10 +4316,10 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-extract-files@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-7.0.0.tgz#3dc7853320ff7876ec62d6e98f2f4e6f3e6282f6"
- integrity sha512-3AUlT7TD+DbQXNe3t70QrgJU6Wgcp7rk1Zm0vqWz8OYnw4vxihgG0TgZ2SIGrVqScc4WfOu7B4a0BezGJ0YqvQ==
+extract-files@^8.0.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-8.1.0.tgz#46a0690d0fe77411a2e3804852adeaa65cd59288"
+ integrity sha512-PTGtfthZK79WUMk+avLmwx3NGdU8+iVFXC2NMGxKsn0MnihOG2lvumj+AZo8CTwTrwjXDgZ5tztbRlEdRjBonQ==
extsprintf@1.3.0:
version "1.3.0"
@@ -4849,19 +4866,19 @@ graphql-tag@^2.9.2, graphql-tag@~2.10.3:
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03"
integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==
-graphql-tools-fork@9.0.1:
- version "9.0.1"
- resolved "https://registry.yarnpkg.com/graphql-tools-fork/-/graphql-tools-fork-9.0.1.tgz#fc8df40c108bdba3268999dea355cc614c765038"
- integrity sha512-kM6mUNVekgnWKtVqLGQ9HvQqQ3zZVPZRg1esltBoohsbUMaChl+9QkjBjoMxnZPnbTGOOGGagopNBQALIBysNg==
+graphql-tools@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-5.0.0.tgz#67281c834a0e29f458adba8018f424816fa627e9"
+ integrity sha512-5zn3vtn//382b7G3Wzz3d5q/sh+f7tVrnxeuhTMTJ7pWJijNqLxH7VEzv8VwXCq19zAzHYEosFHfXiK7qzvk7w==
dependencies:
- apollo-link "^1.2.13"
- apollo-link-http-common "^0.2.15"
+ apollo-link "^1.2.14"
+ apollo-upload-client "^13.0.0"
deprecated-decorator "^0.1.6"
- extract-files "^7.0.0"
form-data "^3.0.0"
iterall "^1.3.0"
node-fetch "^2.6.0"
- uuid "^7.0.2"
+ tslib "^1.11.1"
+ uuid "^7.0.3"
graphql-tools@^4.0.0, graphql-tools@^4.0.4, graphql-tools@^4.0.5:
version "4.0.8"
@@ -6606,13 +6623,13 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
-merge-graphql-schemas@^1.7.7:
- version "1.7.7"
- resolved "https://registry.yarnpkg.com/merge-graphql-schemas/-/merge-graphql-schemas-1.7.7.tgz#673898f97c384d88fd66e49359b2f604e5864441"
- integrity sha512-3mjFHXpsF+bnsnSy/FlZiWCcNo7AULuG8kEBE+YVX8qbzNrEPJVhSg7ANr5LjQZbGtSlTz7fa3Wqw0bfbQ5R5Q==
+merge-graphql-schemas@^1.7.8:
+ version "1.7.8"
+ resolved "https://registry.yarnpkg.com/merge-graphql-schemas/-/merge-graphql-schemas-1.7.8.tgz#11a0a672a38a61d988c09ffdebe1bd4f8418de48"
+ integrity sha512-C3EJ1i86OjmbcCT524wVPRl17M5VZzgyh9kIGYAlYnAILX+7xfh8cCbMKfehh9n4opZg6CtcPogCiVZ6PB2NyQ==
dependencies:
- "@graphql-toolkit/file-loading" "0.9.12"
- "@graphql-toolkit/schema-merging" "0.9.12"
+ "@graphql-toolkit/file-loading" "0.10.4"
+ "@graphql-toolkit/schema-merging" "0.10.4"
tslib "1.11.1"
merge-stream@^2.0.0:
@@ -6942,10 +6959,10 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-mustache@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.0.1.tgz#d99beb031701ad433338e7ea65e0489416c854a2"
- integrity sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==
+mustache@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
+ integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
mute-stream@0.0.8:
version "0.0.8"
@@ -7816,10 +7833,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
-prettier@~2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.0.tgz#8a03c7777883b29b37fb2c4348c66a78e980418b"
- integrity sha512-yYerpkvseM4iKD/BXLYUkQV5aKt4tQPqaGW6EsZjzyu0r7sVZZNPJW4Y8MyKmicp6t42XUPcBVA+H6sB3gqndw==
+prettier@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d"
+ integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==
pretty-format@^25.3.0:
version "25.3.0"
@@ -9406,15 +9423,15 @@ ts-invariant@^0.4.0:
dependencies:
tslib "^1.9.3"
-tslib@1.11.1, tslib@^1.9.0:
+tslib@1.11.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
-tslib@^1.10.0, tslib@^1.9.3:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
- integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
+tslib@^1.10.0, tslib@^1.11.1, tslib@^1.9.0, tslib@^1.9.3:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+ integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tunnel-agent@^0.6.0:
version "0.6.0"
@@ -9689,7 +9706,7 @@ uuid@^3.1.0, uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
-uuid@^7.0.2:
+uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
@@ -9997,9 +10014,9 @@ yallist@^4.0.0:
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^18.1.1:
- version "18.1.1"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.1.tgz#bf7407b915427fc760fcbbccc6c82b4f0ffcbd37"
- integrity sha512-KRHEsOM16IX7XuLnMOqImcPNbLVXMNHYAoFc3BKR8Ortl5gzDbtXvvEoGx9imk5E+X1VeNKNlcHr8B8vi+7ipA==
+ version "18.1.3"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
+ integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
diff --git a/cypress/README.md b/cypress/README.md
index fb94cc522..d9e235786 100644
--- a/cypress/README.md
+++ b/cypress/README.md
@@ -13,16 +13,13 @@ $ docker-compose up
## Setup without docker
-First, you have to tell cypress how to connect to your local neo4j database
-among other things. You can copy our template configuration and change the new
-file according to your needs.
+To start the services that are required for cypress testing manually. You basically need the whole setup to run:
-To start the services that are required for cypress testing, run:
+- backend
+- webapp
+- neo4j
-```bash
-# in the top level folder Ocelot-Social/
-$ yarn cypress:setup
-```
+Navigate to the corresponding folders and start the services.
## Install cypress
@@ -35,21 +32,11 @@ without docker, you would have to install cypress and its dependencies first:
$ yarn install
```
-## Run cypress
-
-After verifying that there are no errors with the servers starting, open another tab in your terminal and run the following command:
-
-```bash
-$ yarn cypress:run
-```
-
-
-
### Open Interactive Test Console
-If you are like me, you might want to see some visual output. The interactive cypress environment also helps at debugging your tests, you can even time travel between individual steps and see the exact state of the app.
+The interactive cypress test console allows to run tests and have visual feedback on that. The interactive cypress environment also helps at debugging the tests, you can even time travel between individual steps and see the exact state of the app.
-To use this feature, instead of `yarn cypress:run` you would run the following command:
+To use this feature run:
```bash
$ yarn cypress:open
@@ -57,7 +44,19 @@ $ yarn cypress:open

+## Run cypress
+
+To run cypress without the user interface:
+
+```bash
+$ yarn cypress:run
+```
+
+This is used to run cypress in CI or in console
+
+
+
## Write some Tests
Check out the Cypress documentation for further information on how to write tests:
-[https://docs.cypress.io/guides/getting-started/writing-your-first-test.html\#Write-a-simple-test](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html#Write-a-simple-test)
+[Write-a-simple-test](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html#Write-a-simple-test)
diff --git a/cypress/constants/terms-and-conditions-version.js b/cypress/constants/terms-and-conditions-version.js
deleted file mode 100644
index 7b2a8fb5d..000000000
--- a/cypress/constants/terms-and-conditions-version.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// please change also version in file "webapp/constants/terms-and-conditions-version.js"
-export const VERSION = '0.0.4'
\ No newline at end of file
diff --git a/cypress.json b/cypress/cypress.json
similarity index 55%
rename from cypress.json
rename to cypress/cypress.json
index 284bdbd34..3f41ca3e5 100644
--- a/cypress.json
+++ b/cypress/cypress.json
@@ -1,8 +1,10 @@
{
"projectId": "qa7fe2",
"ignoreTestFiles": "*.js",
+ "chromeWebSecurity": false,
"baseUrl": "http://localhost:3000",
- "env": {
- "RETRIES": 2
+ "retries": {
+ "runMode": 2,
+ "openMode": 0
}
}
diff --git a/cypress/integration/Admin.PinPost.feature b/cypress/integration/Admin.PinPost.feature
new file mode 100644
index 000000000..a5297d894
--- /dev/null
+++ b/cypress/integration/Admin.PinPost.feature
@@ -0,0 +1,43 @@
+Feature: Admin pins a post
+ As an admin
+ I want to pin a post so that it always appears at the top
+ In order to make sure all network users read it
+ e.g. notify people about security incidents, maintenance downtimes
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
+ | user | user@example.org | abcd | user | User-Chad | user | 0.0.4 |
+ | admin | admin@example.org | 1234 | admin | Admin-Man | admin | 0.0.4 |
+ Given the following "posts" are in the database:
+ | id | title | pinned | createdAt |
+ | p1 | Some other post | | 2020-01-21 |
+ | p2 | Houston we have a problem | x | 2020-01-20 |
+ | p3 | Yet another post | | 2020-01-19 |
+
+ Scenario: Pinned post always appears on the top of the newsfeed
+ When I am logged in as "user"
+ And I navigate to page "/"
+ Then the first post on the newsfeed has the title:
+ """
+ Houston we have a problem
+ """
+ And the post with title "Houston we have a problem" has a ribbon for pinned posts
+
+ Scenario: Ordinary users cannot pin a post
+ When I am logged in as "user"
+ And I navigate to page "/"
+ And I open the content menu of post "Yet another post"
+ Then there is no button to pin a post
+
+ Scenario: Admins are allowed to pin a post
+ When I am logged in as "admin"
+ And I navigate to page "/"
+ And I open the content menu of post "Yet another post"
+ And I click on "pin post"
+ Then I see a toaster with "Post pinned successfully"
+ And the first post on the newsfeed has the title:
+ """
+ Yet another post
+ """
+ And the post with title "Yet another post" has a ribbon for pinned posts
diff --git a/cypress/integration/Admin.PinPost/I_open_the_content_menu_of_post_{string}.js b/cypress/integration/Admin.PinPost/I_open_the_content_menu_of_post_{string}.js
new file mode 100644
index 000000000..78e9ab1ea
--- /dev/null
+++ b/cypress/integration/Admin.PinPost/I_open_the_content_menu_of_post_{string}.js
@@ -0,0 +1,7 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I open the content menu of post {string}", (title)=> {
+ cy.contains('.post-teaser', title)
+ .find('.content-menu .base-button')
+ .click()
+})
\ No newline at end of file
diff --git a/cypress/integration/Admin.PinPost/the_post_with_title_{string}_has_a_ribbon_for_pinned_posts.js b/cypress/integration/Admin.PinPost/the_post_with_title_{string}_has_a_ribbon_for_pinned_posts.js
new file mode 100644
index 000000000..1db51d2b0
--- /dev/null
+++ b/cypress/integration/Admin.PinPost/the_post_with_title_{string}_has_a_ribbon_for_pinned_posts.js
@@ -0,0 +1,9 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the post with title {string} has a ribbon for pinned posts", (title) => {
+ cy.get(".post-teaser").contains(title)
+ .parent()
+ .parent()
+ .find(".ribbon.--pinned")
+ .should("contain", "Announcement")
+})
\ No newline at end of file
diff --git a/cypress/integration/Admin.PinPost/there_is_no_button_to_pin_a_post.js b/cypress/integration/Admin.PinPost/there_is_no_button_to_pin_a_post.js
new file mode 100644
index 000000000..859b9faf1
--- /dev/null
+++ b/cypress/integration/Admin.PinPost/there_is_no_button_to_pin_a_post.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("there is no button to pin a post", () => {
+ cy.get("a.ds-menu-item-link")
+ .should('contain', "Report Post") // sanity check
+ .should('not.contain', "Pin post")
+})
\ No newline at end of file
diff --git a/cypress/integration/Admin.TagOverview.feature b/cypress/integration/Admin.TagOverview.feature
new file mode 100644
index 000000000..fcec638ec
--- /dev/null
+++ b/cypress/integration/Admin.TagOverview.feature
@@ -0,0 +1,31 @@
+Feature: Admin tag overview
+ As a database administrator
+ I would like to see a overview of all tags and their usage
+ In order to be able to decide which tags are popular or not
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
+ | admin | admin@example.org | 1234 | admin | Admin-Man | admin | 0.0.4 |
+ | u1 | u1@example.org | 1234 | u1 | User1 | user | 0.0.4 |
+ | u2 | u2@example.org | 1234 | u2 | User2 | user | 0.0.4 |
+ | u3 | u3@example.org | 1234 | u3 | User3 | user | 0.0.4 |
+ And the following "tags" are in the database:
+ | id |
+ | Ecology |
+ | Nature |
+ | Democracy |
+ And the following "posts" are in the database:
+ | id | title | authorId | tagIds |
+ | p1 | P1 from U1 | u1 | Nature, Democracy |
+ | p2 | P2 from U2 | u2 | Ecology, Democracy |
+ | p3 | P3 from U3 | u3 | Nature, Democracy |
+ And I am logged in as "admin"
+
+ Scenario: See an overview of tags
+ When I navigate to page "/admin/hashtags"
+ Then I can see the following table:
+ | No. | Hashtags | Users | Posts |
+ | 1 | #Democracy | 3 | 3 |
+ | 2 | #Nature | 2 | 2 |
+ | 3 | #Ecology | 1 | 1 |
\ No newline at end of file
diff --git a/cypress/integration/internationalization/Internationalization.feature b/cypress/integration/Internationalization.feature
similarity index 89%
rename from cypress/integration/internationalization/Internationalization.feature
rename to cypress/integration/Internationalization.feature
index 18070d888..5eb6bbc3f 100644
--- a/cypress/integration/internationalization/Internationalization.feature
+++ b/cypress/integration/Internationalization.feature
@@ -4,7 +4,7 @@ Feature: Internationalization
In order to be able to understand the interface
Background:
- Given I am on the "login" page
+ Given I navigate to page "/login"
Scenario Outline: I select "" in the language menu and see ""
When I select "" in the language menu
@@ -18,6 +18,6 @@ Feature: Internationalization
| English | Login |
Scenario: Keep preferred language after refresh
- Given I previously switched the language to "Français"
+ When I select "Français" in the language menu
And I refresh the page
Then the whole user interface appears in "Français"
diff --git a/cypress/integration/Internationalization/I_see_a_button_with_the_label_{string}.js b/cypress/integration/Internationalization/I_see_a_button_with_the_label_{string}.js
new file mode 100644
index 000000000..a67f9d7df
--- /dev/null
+++ b/cypress/integration/Internationalization/I_see_a_button_with_the_label_{string}.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I see a button with the label {string}", label => {
+ cy.contains("button", label);
+});
\ No newline at end of file
diff --git a/cypress/integration/Internationalization/I_select_{string}_in_the_language_menu.js b/cypress/integration/Internationalization/I_select_{string}_in_the_language_menu.js
new file mode 100644
index 000000000..b850a7573
--- /dev/null
+++ b/cypress/integration/Internationalization/I_select_{string}_in_the_language_menu.js
@@ -0,0 +1,8 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I select {string} in the language menu", language => {
+ cy.get(".locale-menu")
+ .click();
+ cy.contains(".locale-menu-popover a", language)
+ .click();
+});
\ No newline at end of file
diff --git a/cypress/integration/Internationalization/the_whole_user_interface_appears_in_{string}.js b/cypress/integration/Internationalization/the_whole_user_interface_appears_in_{string}.js
new file mode 100644
index 000000000..4d80b8a0d
--- /dev/null
+++ b/cypress/integration/Internationalization/the_whole_user_interface_appears_in_{string}.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+import locales from '../../../webapp/locales'
+
+Then("the whole user interface appears in {string}", language => {
+ const { code } = locales.find((entry) => entry.name === language);
+ cy.get(`html[lang=${code}]`);
+ cy.getCookie("locale").should("have.property", "value", code);
+});
\ No newline at end of file
diff --git a/cypress/integration/Moderation.HidePost.feature b/cypress/integration/Moderation.HidePost.feature
new file mode 100644
index 000000000..0ef802267
--- /dev/null
+++ b/cypress/integration/Moderation.HidePost.feature
@@ -0,0 +1,40 @@
+Feature: Hide Posts
+ As a moderator
+ I would like to be able to hide posts from the public
+ to enforce our network's code of conduct and/or legal regulations
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
+ | user | user@example.org | abcd | user | User-Chad | user | 0.0.4 |
+ | moderator | moderator@example.org | 1234 | moderator | Mod-Man | moderator | 0.0.4 |
+ Given the following "posts" are in the database:
+ | id | title | deleted | disabled |
+ | p1 | This post should be visible | | |
+ | p2 | This post is disabled | | x |
+ | p3 | This post is deleted | x | |
+
+ Scenario: Disabled posts don't show up on the newsfeed as user
+ When I am logged in as "user"
+ And I navigate to page "/"
+ Then I should see only 1 posts on the newsfeed
+ And the first post on the newsfeed has the title:
+ """
+ This post should be visible
+ """
+
+ Scenario: Disabled posts show up on the newsfeed as moderator
+ When I am logged in as "moderator"
+ And I navigate to page "/"
+ Then I should see only 2 posts on the newsfeed
+ And the first post on the newsfeed has the title:
+ """
+ This post is disabled
+ """
+
+ Scenario: Visiting a disabled post's page should return 404
+ Given I am logged in as "user"
+ Then the page "/post/this-post-is-disabled" returns a 404 error with a message:
+ """
+ This post could not be found
+ """
diff --git a/cypress/integration/Moderation.HidePost/I_should_see_only_{int}_posts_on_the_newsfeed.js b/cypress/integration/Moderation.HidePost/I_should_see_only_{int}_posts_on_the_newsfeed.js
new file mode 100644
index 000000000..611365bb0
--- /dev/null
+++ b/cypress/integration/Moderation.HidePost/I_should_see_only_{int}_posts_on_the_newsfeed.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see only {int} posts on the newsfeed", posts => {
+ cy.get(".post-teaser")
+ .should("have.length", posts);
+});
+
\ No newline at end of file
diff --git a/cypress/integration/Moderation.HidePost/the_page_{string}_returns_a_404_error_with_a_message.js b/cypress/integration/Moderation.HidePost/the_page_{string}_returns_a_404_error_with_a_message.js
new file mode 100644
index 000000000..6d9cfb2ef
--- /dev/null
+++ b/cypress/integration/Moderation.HidePost/the_page_{string}_returns_a_404_error_with_a_message.js
@@ -0,0 +1,14 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the page {string} returns a 404 error with a message:", (route, message) => {
+ cy.request({
+ url: route,
+ failOnStatusCode: false
+ })
+ .its("status")
+ .should("eq", 404);
+ cy.visit(route, {
+ failOnStatusCode: false
+ });
+ cy.get(".error-message").should("contain", message);
+});
\ No newline at end of file
diff --git a/cypress/integration/moderation/ReportContent.feature b/cypress/integration/Moderation.ReportContent.feature
similarity index 61%
rename from cypress/integration/moderation/ReportContent.feature
rename to cypress/integration/Moderation.ReportContent.feature
index be1a07786..518020bd0 100644
--- a/cypress/integration/moderation/ReportContent.feature
+++ b/cypress/integration/Moderation.ReportContent.feature
@@ -8,51 +8,47 @@ Feature: Report and Moderate
So I can look into it and decide what to do
Background:
- Given we have the following user accounts:
- | id | name |
- | u67 | David Irving |
- | annoying-user | I'm gonna mute Moderators and Admins HA HA HA |
-
- Given we have the following posts in our database:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
+ | user | user@example.org | abcd | user | User-Chad | user | 0.0.4 |
+ | moderator | moderator@example.org | 1234 | moderator | Mod-Man | moderator | 0.0.4 |
+ | annoying | annoying@example.org | 1234 | annoying-user | I'm gonna mute Moderators and Admins HA HA HA | user | 0.0.4 |
+ And the following "posts" are in the database:
| authorId | id | title | content |
- | u67 | p1 | The Truth about the Holocaust | It never existed! |
+ | annoying-user | p1 | The Truth about the Holocaust | It never existed! |
| annoying-user | p2 | Fake news | This content is demonstratably infactual in some way |
+
Scenario Outline: Report a post from various pages
- Given I am logged in with a "user" role
- When I see David Irving's post on the
+ When I am logged in as "user"
+ And I navigate to page ""
And I click on "Report Post" from the content menu of the post
And I confirm the reporting dialog because it is a criminal act under German law:
"""
Do you really want to report the contribution "The Truth about the Holocaust"?
"""
- Then I see a success message:
- """
- Thanks for reporting!
- """
+ Then I see a toaster with "Thanks for reporting!"
Examples:
- | Page |
- | landing page |
- | post page |
+ | Page |
+ | / |
+ | /post/p1 |
Scenario: Report user
- Given I am logged in with a "user" role
- And I see David Irving's post on the post page
+ Given I am logged in as "user"
+ And I navigate to page "/post/the-truth-about-the-holocaust"
When I click on the author
And I click on "Report User" from the content menu in the user info box
And I confirm the reporting dialog because he is a holocaust denier:
"""
- Do you really want to report the user "David Irving"?
- """
- Then I see a success message:
- """
- Thanks for reporting!
+ Do you really want to report the user "I'm gonna mute Moderators and …"?
"""
+ Then I see a toaster with "Thanks for reporting!"
Scenario: Review reported content
Given somebody reported the following posts:
| submitterEmail | resourceId | reasonCategory | reasonDescription |
| p1.submitter@example.org | p1 | discrimination_etc | Offensive content |
- And I am logged in with a "moderator" role
+ And I am logged in as "moderator"
+ And I navigate to page "/"
When I click on the avatar menu in the top right corner
And I click on "Moderation"
Then I see all the reported posts including the one from above
@@ -62,7 +58,8 @@ Feature: Report and Moderate
Given somebody reported the following posts:
| submitterEmail | resourceId | reasonCategory | reasonDescription |
| p2.submitter@example.org | p2 | other | Offensive content |
- And I am logged in with a "moderator" role
+ And I am logged in as "moderator"
+ And I navigate to page "/"
And there is an annoying user who has muted me
When I click on the avatar menu in the top right corner
And I click on "Moderation"
@@ -70,6 +67,7 @@ Feature: Report and Moderate
And I can visit the post page
Scenario: Normal user can't see the moderation page
- Given I am logged in with a "user" role
+ Given I am logged in as "user"
+ And I navigate to page "/"
When I click on the avatar menu in the top right corner
Then I can't see the moderation menu item
diff --git a/cypress/integration/Moderation.ReportContent/I_can_t_see_the_moderation_menu_item.js b/cypress/integration/Moderation.ReportContent/I_can_t_see_the_moderation_menu_item.js
new file mode 100644
index 000000000..96706281a
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_can_t_see_the_moderation_menu_item.js
@@ -0,0 +1,11 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then(`I can't see the moderation menu item`, () => {
+ cy.get('.avatar-menu-popover')
+ .find('a[href="/settings"]', 'Settings')
+ .should('exist') // OK, the dropdown is actually open
+
+ cy.get('.avatar-menu-popover')
+ .find('a[href="/moderation"]', 'Moderation')
+ .should('not.exist')
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/I_can_visit_the_post_page.js b/cypress/integration/Moderation.ReportContent/I_can_visit_the_post_page.js
new file mode 100644
index 000000000..8ca69da50
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_can_visit_the_post_page.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I can visit the post page', () => {
+ cy.contains('Fake news').click()
+ cy.location('pathname').should('contain', '/post')
+ .get('.base-card .title').should('contain', 'Fake news')
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/I_click_on_Report_Post_from_the_content_menu_of_the_post.js b/cypress/integration/Moderation.ReportContent/I_click_on_Report_Post_from_the_content_menu_of_the_post.js
new file mode 100644
index 000000000..30682b009
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_click_on_Report_Post_from_the_content_menu_of_the_post.js
@@ -0,0 +1,11 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I click on "Report Post" from the content menu of the post', () => {
+ cy.contains('.base-card', 'The Truth about the Holocaust')
+ .find('.content-menu .base-button')
+ .click({force: true})
+
+ cy.get('.popover .ds-menu-item-link')
+ .contains('Report Post')
+ .click()
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/I_click_on_the_author.js b/cypress/integration/Moderation.ReportContent/I_click_on_the_author.js
new file mode 100644
index 000000000..3a6600ff6
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_click_on_the_author.js
@@ -0,0 +1,7 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I click on the author', () => {
+ cy.get('.user-teaser')
+ .click()
+ .url().should('include', '/profile/')
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/I_click_on_the_avatar_menu_in_the_top_right_corner.js b/cypress/integration/Moderation.ReportContent/I_click_on_the_avatar_menu_in_the_top_right_corner.js
new file mode 100644
index 000000000..27830b239
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_click_on_the_avatar_menu_in_the_top_right_corner.js
@@ -0,0 +1,5 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I click on the avatar menu in the top right corner", () => {
+ cy.get(".avatar-menu").click();
+});
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/I_confirm_the_reporting_dialog.js b/cypress/integration/Moderation.ReportContent/I_confirm_the_reporting_dialog.js
new file mode 100644
index 000000000..4009fa4e8
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_confirm_the_reporting_dialog.js
@@ -0,0 +1,16 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When(/^I confirm the reporting dialog .*:$/, message => {
+ cy.contains(message) // wait for element to become visible
+ cy.get('.ds-modal')
+ .within(() => {
+ cy.get('.ds-radio-option-label')
+ .first()
+ .click({
+ force: true
+ })
+ cy.get('button')
+ .contains('Report')
+ .click()
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/I_see_all_the_reported_posts_including_from_the_user_who_muted_me.js b/cypress/integration/Moderation.ReportContent/I_see_all_the_reported_posts_including_from_the_user_who_muted_me.js
new file mode 100644
index 000000000..522cd6c78
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_see_all_the_reported_posts_including_from_the_user_who_muted_me.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I see all the reported posts including from the user who muted me', () => {
+ cy.get('table tbody').within(() => {
+ cy.contains('tr', 'Fake news')
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/I_see_all_the_reported_posts_including_the_one_from_above.js b/cypress/integration/Moderation.ReportContent/I_see_all_the_reported_posts_including_the_one_from_above.js
new file mode 100644
index 000000000..66c9baf61
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/I_see_all_the_reported_posts_including_the_one_from_above.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I see all the reported posts including the one from above', () => {
+ cy.get('table tbody').within(() => {
+ cy.contains('tr', 'The Truth about the Holocaust')
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/each_list_item_links_to_the_post_page.js b/cypress/integration/Moderation.ReportContent/each_list_item_links_to_the_post_page.js
new file mode 100644
index 000000000..9ce69d6de
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/each_list_item_links_to_the_post_page.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('each list item links to the post page', () => {
+ cy.contains('The Truth about the Holocaust').click();
+ cy.location('pathname').should('contain', '/post')
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/somebody_reported_the_following_posts.js b/cypress/integration/Moderation.ReportContent/somebody_reported_the_following_posts.js
new file mode 100644
index 000000000..ce876a081
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/somebody_reported_the_following_posts.js
@@ -0,0 +1,23 @@
+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 }) => {
+ const submitter = {
+ email: submitterEmail,
+ password: '1234'
+ }
+ cy.factory()
+ .build('user', {}, submitter)
+ .authenticateAs(submitter)
+ .mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
+ fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
+ reportId
+ }
+ }`, {
+ resourceId,
+ reasonCategory,
+ reasonDescription
+ })
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/Moderation.ReportContent/there_is_an_annoying_user_who_has_muted_me.js b/cypress/integration/Moderation.ReportContent/there_is_an_annoying_user_who_has_muted_me.js
new file mode 100644
index 000000000..8d475ee43
--- /dev/null
+++ b/cypress/integration/Moderation.ReportContent/there_is_an_annoying_user_who_has_muted_me.js
@@ -0,0 +1,13 @@
+Given("there is an annoying user who has muted me", () => {
+ cy.neode()
+ .first("User", {
+ role: 'moderator'
+ })
+ .then(mutedUser => {
+ cy.neode()
+ .first("User", {
+ id: 'user'
+ })
+ .relateTo(mutedUser, "muted");
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/Notification.Mention.feature b/cypress/integration/Notification.Mention.feature
new file mode 100644
index 000000000..cadfe11dd
--- /dev/null
+++ b/cypress/integration/Notification.Mention.feature
@@ -0,0 +1,29 @@
+Feature: Notification for a mention
+ As a user
+ I want to be notified if somebody mentions me in a post or comment
+ In order join conversations about or related to me
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | termsAndConditionsAgreedVersion |
+ | wolle-aus-hamburg | wolle@example.org | 1234 | wolle | Wolle aus Hamburg | 0.0.4 |
+ | matt-rider | matt@example.org | 4321 | matt | Matt Rider | 0.0.4 |
+
+ Scenario: Mention another user, re-login as this user and see notifications
+ Given I am logged in as "wolle-aus-hamburg"
+ And I navigate to page "/"
+ And I navigate to page "/post/create"
+ And I start to write a new post with the title "Hey Matt" beginning with:
+ """
+ Big shout to our fellow contributor
+ """
+ And mention "@matt-rider" in the text
+ And I click on "save button"
+ And I am logged in as "matt-rider"
+ And I navigate to page "/"
+ And see 1 unread notifications in the top menu
+ And open the notification menu and click on the first item
+ And I wait for 750 milliseconds
+ Then I am on page "/post/.*/hey-matt"
+ And the unread counter is removed
+ And the notification menu button links to the all notifications page
\ No newline at end of file
diff --git a/cypress/integration/Notification.Mention/I_start_to_write_a_new_post_with_the_title_{string}_beginning_with.js b/cypress/integration/Notification.Mention/I_start_to_write_a_new_post_with_the_title_{string}_beginning_with.js
new file mode 100644
index 000000000..fde5042c1
--- /dev/null
+++ b/cypress/integration/Notification.Mention/I_start_to_write_a_new_post_with_the_title_{string}_beginning_with.js
@@ -0,0 +1,8 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I start to write a new post with the title {string} beginning with:", (title, intro) => {
+ cy.get('input[name="title"]')
+ .type(title);
+ cy.get(".ProseMirror")
+ .type(intro);
+});
\ No newline at end of file
diff --git a/cypress/integration/Notification.Mention/mention_{string}_in_the_text.js b/cypress/integration/Notification.Mention/mention_{string}_in_the_text.js
new file mode 100644
index 000000000..fa8a29d4a
--- /dev/null
+++ b/cypress/integration/Notification.Mention/mention_{string}_in_the_text.js
@@ -0,0 +1,9 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("mention {string} in the text", mention => {
+ cy.get(".ProseMirror")
+ .type(" @");
+ cy.get(".suggestion-list__item")
+ .contains(mention)
+ .click();
+});
\ No newline at end of file
diff --git a/cypress/integration/Notification.Mention/open_the_notification_menu_and_click_on_the_first_item.js b/cypress/integration/Notification.Mention/open_the_notification_menu_and_click_on_the_first_item.js
new file mode 100644
index 000000000..0d3917f38
--- /dev/null
+++ b/cypress/integration/Notification.Mention/open_the_notification_menu_and_click_on_the_first_item.js
@@ -0,0 +1,10 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("open the notification menu and click on the first item", () => {
+ cy.get(".notifications-menu")
+ .invoke('show')
+ .click(); // "invoke('show')" because of the delay for show the menu
+ cy.get(".notification .link")
+ .first()
+ .click({force: true});
+});
\ No newline at end of file
diff --git a/cypress/integration/Notification.Mention/see_{int}_unread_notifications_in_the_top_menu.js b/cypress/integration/Notification.Mention/see_{int}_unread_notifications_in_the_top_menu.js
new file mode 100644
index 000000000..124b61873
--- /dev/null
+++ b/cypress/integration/Notification.Mention/see_{int}_unread_notifications_in_the_top_menu.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("see {int} unread notifications in the top menu", count => {
+ cy.get(".notifications-menu")
+ .should("contain", count);
+});
\ No newline at end of file
diff --git a/cypress/integration/Notification.Mention/the_notification_menu_button_links_to_the_all_notifications_page.js b/cypress/integration/Notification.Mention/the_notification_menu_button_links_to_the_all_notifications_page.js
new file mode 100644
index 000000000..e40827a16
--- /dev/null
+++ b/cypress/integration/Notification.Mention/the_notification_menu_button_links_to_the_all_notifications_page.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the notification menu button links to the all notifications page", () => {
+ cy.get(".notifications-menu")
+ .click();
+ cy.location("pathname")
+ .should("contain", "/notifications");
+});
\ No newline at end of file
diff --git a/cypress/integration/Notification.Mention/the_unread_counter_is_removed.js b/cypress/integration/Notification.Mention/the_unread_counter_is_removed.js
new file mode 100644
index 000000000..3859103e8
--- /dev/null
+++ b/cypress/integration/Notification.Mention/the_unread_counter_is_removed.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the unread counter is removed", () => {
+ cy.get('.notifications-menu .counter-icon')
+ .should('not.exist');
+});
\ No newline at end of file
diff --git a/cypress/integration/PersistentLinks.feature b/cypress/integration/PersistentLinks.feature
new file mode 100644
index 000000000..89f9d9654
--- /dev/null
+++ b/cypress/integration/PersistentLinks.feature
@@ -0,0 +1,31 @@
+Feature: Persistent Links
+ As a user
+ I want all links to carry permanent information that identifies the linked resource
+ In order to have persistent links even if a part of the URL might change
+ | | Modifiable | Referenceable | Unique | Purpose |
+ | -- | -- | -- | -- | -- |
+ | ID | no | yes | yes | Identity, Traceability, Links |
+ | Slug | yes | yes | yes | @-Mentions, SEO-friendly URL |
+ | Name | yes | no | no | Search, self-description |
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | termsAndConditionsAgreedVersion |
+ | thehawk | hawk@example.org | abcd | MHNqce98y1 | Stephen Hawking | 0.0.4 |
+ | narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
+ And the following "posts" are in the database:
+ | id | title | slug |
+ | bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays |
+ And I am logged in as "narrator"
+
+ Scenario Outline: Link with healable information is valid and gets auto-completed
+ When I navigate to page ""
+ Then I am on page ""
+ Examples:
+ | url | redirectUrl | reason |
+ | /profile/thehawk | /profile/MHNqce98y1/thehawk | Identifiable user slug |
+ | /post/101-essays | /post/bWBjpkTKZp/101-essays | Identifiable post slug |
+ | /profile/MHNqce98y1 | /profile/MHNqce98y1/thehawk | Identifiable user ID |
+ | /post/bWBjpkTKZp | /post/bWBjpkTKZp/101-essays | Identifiable post ID |
+ | /profile/MHNqce98y1/stephen-hawking | /profile/MHNqce98y1/thehawk | Identifiable user ID takes precedence over slug |
+ | /post/bWBjpkTKZp/the-way-you-think | /post/bWBjpkTKZp/101-essays | Identifiable post ID takes precedence over slug |
diff --git a/cypress/integration/Post.Comment.feature b/cypress/integration/Post.Comment.feature
new file mode 100644
index 000000000..1ec0c602a
--- /dev/null
+++ b/cypress/integration/Post.Comment.feature
@@ -0,0 +1,49 @@
+Feature: Comments on post
+ As a user
+ I want to comment and see comments on contributions of others
+ To be able to express my thoughts and emotions about these, discuss, and add give further information.
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | termsAndConditionsAgreedVersion |
+ | peter-pan| peter@pan.com | abcd | id-of-peter-pan| Peter Pan | 0.0.4 |
+ | narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
+ And the following "posts" are in the database:
+ | id | title | slug | authorId |
+ | bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | id-of-peter-pan |
+ And the following "comments" are in the database:
+ | postId | content | authorId |
+ | bWBjpkTKZp | @peter-pan reply to me | id-of-peter-pan |
+ And I am logged in as "narrator"
+
+ Scenario: Comment creation
+ Given I navigate to page "/post/bWBjpkTKZp/101-essays"
+ And I comment the following:
+ """
+ Ocelot.social rocks
+ """
+ And I click on "comment button"
+ Then my comment should be successfully created
+ And I should see my comment
+ And the editor should be cleared
+
+ Scenario: View medium length comments
+ Given I navigate to page "/post/bWBjpkTKZp/101-essays"
+ And I type in a comment with 305 characters
+ And I click on "comment button"
+ Then my comment should be successfully created
+ And I should see the entirety of my comment
+ And the editor should be cleared
+
+ Scenario: View long comments
+ Given I navigate to page "/post/bWBjpkTKZp/101-essays"
+ And I type in a comment with 1205 characters
+ And I click on "comment button"
+ Then my comment should be successfully created
+ And I should see an abbreviated version of my comment
+ And the editor should be cleared
+
+ Scenario: Direct reply to Comment
+ Given I navigate to page "/post/bWBjpkTKZp/101-essays"
+ And I click on "reply button"
+ Then it should create a mention in the CommentForm
diff --git a/cypress/integration/Post.Comment/I_comment_the_following.js b/cypress/integration/Post.Comment/I_comment_the_following.js
new file mode 100644
index 000000000..0f5a5049c
--- /dev/null
+++ b/cypress/integration/Post.Comment/I_comment_the_following.js
@@ -0,0 +1,7 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I comment the following:", async text => {
+ const comment = text.replace("\n", " ")
+ cy.task('pushValue', { name: 'lastComment', value: comment })
+ cy.get(".editor .ProseMirror").type(comment);
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Comment/I_should_see_an_abbreviated_version_of_my_comment.js b/cypress/integration/Post.Comment/I_should_see_an_abbreviated_version_of_my_comment.js
new file mode 100644
index 000000000..d0b7940f0
--- /dev/null
+++ b/cypress/integration/Post.Comment/I_should_see_an_abbreviated_version_of_my_comment.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see an abbreviated version of my comment", () => {
+ cy.get("article.comment-card")
+ .should("contain", "show more")
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Comment/I_should_see_my_comment.js b/cypress/integration/Post.Comment/I_should_see_my_comment.js
new file mode 100644
index 000000000..356593f9c
--- /dev/null
+++ b/cypress/integration/Post.Comment/I_should_see_my_comment.js
@@ -0,0 +1,13 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see my comment", () => {
+ cy.get("article.comment-card p")
+ .should("contain", "Ocelot.social rocks")
+ .get(".user-teaser span.slug")
+ .should("contain", "@peter-pan") // specific enough
+ .get(".user-avatar img")
+ .should("have.attr", "src")
+ .and("contain", 'https://') // some url
+ .get(".user-teaser > .info > .text")
+ .should("contain", "today at");
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Comment/I_should_see_the_entirety_of_my_comment.js b/cypress/integration/Post.Comment/I_should_see_the_entirety_of_my_comment.js
new file mode 100644
index 000000000..a903fa4d0
--- /dev/null
+++ b/cypress/integration/Post.Comment/I_should_see_the_entirety_of_my_comment.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see the entirety of my comment", () => {
+ cy.get("article.comment-card")
+ .should("not.contain", "show more")
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Comment/I_type_in_a_comment_with_{int}_characters.js b/cypress/integration/Post.Comment/I_type_in_a_comment_with_{int}_characters.js
new file mode 100644
index 000000000..1522c0e64
--- /dev/null
+++ b/cypress/integration/Post.Comment/I_type_in_a_comment_with_{int}_characters.js
@@ -0,0 +1,9 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I type in a comment with {int} characters", size => {
+ var c="";
+ for (var i = 0; i < size; i++) {
+ c += "c"
+ }
+ cy.get(".editor .ProseMirror").type(c);
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Comment/it_should_create_a_mention_in_the_CommentForm.js b/cypress/integration/Post.Comment/it_should_create_a_mention_in_the_CommentForm.js
new file mode 100644
index 000000000..3468badad
--- /dev/null
+++ b/cypress/integration/Post.Comment/it_should_create_a_mention_in_the_CommentForm.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("it should create a mention in the CommentForm", () => {
+ cy.get(".ProseMirror a")
+ .should('have.class', 'mention')
+ .should('contain', '@peter-pan')
+})
\ No newline at end of file
diff --git a/cypress/integration/Post.Comment/my_comment_should_be_successfully_created.js b/cypress/integration/Post.Comment/my_comment_should_be_successfully_created.js
new file mode 100644
index 000000000..766106ddf
--- /dev/null
+++ b/cypress/integration/Post.Comment/my_comment_should_be_successfully_created.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("my comment should be successfully created", () => {
+ cy.get(".iziToast-message").contains("Comment submitted!");
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Comment/the_editor_should_be_cleared.js b/cypress/integration/Post.Comment/the_editor_should_be_cleared.js
new file mode 100644
index 000000000..579fc2ca9
--- /dev/null
+++ b/cypress/integration/Post.Comment/the_editor_should_be_cleared.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the editor should be cleared", () => {
+ cy.get(".ProseMirror p").should("have.class", "is-empty");
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Create.feature b/cypress/integration/Post.Create.feature
new file mode 100644
index 000000000..cdb3e1008
--- /dev/null
+++ b/cypress/integration/Post.Create.feature
@@ -0,0 +1,24 @@
+Feature: Create a post
+ As an logged in user
+ I would like to create a post
+ To say something to everyone in the community
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | termsAndConditionsAgreedVersion |
+ | narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
+ And I am logged in as "narrator"
+ And I navigate to page "/"
+
+ Scenario: Create a post
+ When I click on "create post button"
+ Then I am on page "post/create"
+ When I choose "My first post" as the title
+ And I choose the following text as content:
+ """
+ Ocelot.social is a free and open-source social network
+ for active citizenship.
+ """
+ And I click on "save button"
+ Then I am on page "/post/.*/my-first-post"
+ And the post was saved successfully
diff --git a/cypress/integration/Post.Create/I_choose_{string}_as_the_title.js b/cypress/integration/Post.Create/I_choose_{string}_as_the_title.js
new file mode 100644
index 000000000..9fbf8e58f
--- /dev/null
+++ b/cypress/integration/Post.Create/I_choose_{string}_as_the_title.js
@@ -0,0 +1,8 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I choose {string} as the title", async title => {
+ const lastPost = {}
+ lastPost.title = title.replace("\n", " ");
+ cy.task('pushValue', { name: 'lastPost', value: lastPost })
+ cy.get('input[name="title"]').type(lastPost.title);
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Create/the_post_was_saved_successfully.js b/cypress/integration/Post.Create/the_post_was_saved_successfully.js
new file mode 100644
index 000000000..eec2f819b
--- /dev/null
+++ b/cypress/integration/Post.Create/the_post_was_saved_successfully.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the post was saved successfully", async () => {
+ cy.task('getValue', 'lastPost').then(lastPost => {
+ cy.get(".base-card > .title").should("contain", lastPost.title);
+ cy.get(".content").should("contain", lastPost.content);
+ })
+});
\ No newline at end of file
diff --git a/cypress/integration/Post.Images.feature b/cypress/integration/Post.Images.feature
new file mode 100644
index 000000000..68c223394
--- /dev/null
+++ b/cypress/integration/Post.Images.feature
@@ -0,0 +1,66 @@
+Feature: Upload/Delete images on posts
+ As a user
+ I would like to be able to add/delete an image to/from my Post
+ So that I can personalize my posts
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | termsAndConditionsAgreedVersion |
+ | narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
+ And the following "posts" are in the database:
+ | authorId | id | title | content |
+ | narrator | p1 | Post to be updated | successfully updated |
+ And I am logged in as "narrator"
+ And I navigate to page "/"
+
+ Scenario: Create a Post with a Teaser Image
+ When I click on "create post button"
+ Then I wait for 750 milliseconds
+ Then I should be able to "add" a teaser image
+ And I add all required fields
+ And I click on "save button"
+ And I wait for 750 milliseconds
+ Then I am on page "/post/.*/new-post"
+ And I wait for 750 milliseconds
+ And the post was saved successfully with the "new" teaser image
+
+ Scenario: Update a Post to add an image
+ Given I navigate to page "/post/edit/p1"
+ Then I wait for 750 milliseconds
+ And I should be able to "change" a teaser image
+ And I click on "save button"
+ Then I see a toaster with "Saved!"
+ And I wait for 750 milliseconds
+ And I am on page "/post/.*/post-to-be-updated"
+ And I wait for 750 milliseconds
+ Then the post was saved successfully with the "updated" teaser image
+
+ Scenario: Add image, then add a different image
+ When I click on "create post button"
+ Then I wait for 750 milliseconds
+ Then I should be able to "add" a teaser image
+ And I should be able to "change" a teaser image
+ And the first image should not be displayed anymore
+
+ Scenario: Add image, then delete it
+ When I click on "create post button"
+ Then I wait for 750 milliseconds
+ Then I should be able to "add" a teaser image
+ Then I should be able to "remove" a teaser image
+ And I add all required fields
+ And I click on "save button"
+ And I wait for 750 milliseconds
+ Then I am on page "/post/.*/new-post"
+ And I wait for 750 milliseconds
+ And the "new" post was saved successfully without a teaser image
+
+ Scenario: Delete existing image
+ Given I navigate to page "/post/edit/p1"
+ Then I wait for 750 milliseconds
+ And my post has a teaser image
+ Then I should be able to "remove" a teaser image
+ And I click on "save button"
+ And I wait for 750 milliseconds
+ Then I am on page "/post/.*/post-to-be-updated"
+ And I wait for 750 milliseconds
+ And the "updated" post was saved successfully without a teaser image
\ No newline at end of file
diff --git a/cypress/integration/Post.Images/I_add_all_required_fields.js b/cypress/integration/Post.Images/I_add_all_required_fields.js
new file mode 100644
index 000000000..52a95ab52
--- /dev/null
+++ b/cypress/integration/Post.Images/I_add_all_required_fields.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I add all required fields", () => {
+ cy.get('input[name="title"]')
+ .type('new post')
+ .get(".editor .ProseMirror")
+ .type('new post content')
+ })
\ No newline at end of file
diff --git a/cypress/integration/Post.Images/I_should_be_able_to_{string}_a_teaser_image.js b/cypress/integration/Post.Images/I_should_be_able_to_{string}_a_teaser_image.js
new file mode 100644
index 000000000..ce5b54f25
--- /dev/null
+++ b/cypress/integration/Post.Images/I_should_be_able_to_{string}_a_teaser_image.js
@@ -0,0 +1,30 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should be able to {string} a teaser image", condition => {
+ // cy.reload()
+ switch(condition){
+ case 'change':
+ cy.get('.delete-image-button')
+ .click()
+ cy.fixture('humanconnection.png').as('postTeaserImage').then(function() {
+ cy.get("#postdropzone").upload(
+ { fileContent: this.postTeaserImage, fileName: 'humanconnection.png', mimeType: "image/png" },
+ { subjectType: "drag-n-drop", force: true }
+ ).wait(750);
+ })
+ break;
+ case 'add':
+ cy.fixture('onourjourney.png').as('postTeaserImage').then(function() {
+ cy.get("#postdropzone").upload(
+ { fileContent: this.postTeaserImage, fileName: 'onourjourney.png', mimeType: "image/png" },
+ { subjectType: "drag-n-drop", force: true }
+ ).wait(750);
+ })
+ break;
+ case 'remove':
+ cy.get('.delete-image-button')
+ .click()
+ break;
+ }
+
+})
\ No newline at end of file
diff --git a/cypress/integration/Post.Images/my_post_has_a_teaser_image.js b/cypress/integration/Post.Images/my_post_has_a_teaser_image.js
new file mode 100644
index 000000000..66ff3c31d
--- /dev/null
+++ b/cypress/integration/Post.Images/my_post_has_a_teaser_image.js
@@ -0,0 +1,7 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('my post has a teaser image', () => {
+ cy.get('.contribution-form .image')
+ .should('exist')
+ .and('have.attr', 'src')
+})
\ No newline at end of file
diff --git a/cypress/integration/Post.Images/the_first_image_should_not_be_displayed_anymore.js b/cypress/integration/Post.Images/the_first_image_should_not_be_displayed_anymore.js
new file mode 100644
index 000000000..867c97fdf
--- /dev/null
+++ b/cypress/integration/Post.Images/the_first_image_should_not_be_displayed_anymore.js
@@ -0,0 +1,9 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the first image should not be displayed anymore", () => {
+ cy.get(".hero-image")
+ .children()
+ .get('.hero-image > .image')
+ .should('have.length', 1)
+ .and('have.attr', 'src')
+})
\ No newline at end of file
diff --git a/cypress/integration/Post.Images/the_post_was_saved_successfully_with_the_{string}_teaser_image.js b/cypress/integration/Post.Images/the_post_was_saved_successfully_with_the_{string}_teaser_image.js
new file mode 100644
index 000000000..ece83d878
--- /dev/null
+++ b/cypress/integration/Post.Images/the_post_was_saved_successfully_with_the_{string}_teaser_image.js
@@ -0,0 +1,11 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the post was saved successfully with the {string} teaser image", condition => {
+ cy.get(".base-card > .title")
+ .should("contain", condition === 'updated' ? 'to be updated' : 'new post')
+ .get(".content")
+ .should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
+ .get('.post-page img')
+ .should("have.attr", "src")
+ .and("contains", condition === 'updated' ? 'humanconnection' : 'onourjourney')
+})
\ No newline at end of file
diff --git a/cypress/integration/Post.Images/the_{string}_post_was_saved_successfully_without_a_teaser_image.js b/cypress/integration/Post.Images/the_{string}_post_was_saved_successfully_without_a_teaser_image.js
new file mode 100644
index 000000000..abafcf0cc
--- /dev/null
+++ b/cypress/integration/Post.Images/the_{string}_post_was_saved_successfully_without_a_teaser_image.js
@@ -0,0 +1,12 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('the {string} post was saved successfully without a teaser image', condition => {
+ cy.get(".base-card > .title")
+ .should("contain", condition === 'updated' ? 'to be updated' : 'new post')
+ .get(".content")
+ .should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
+ .get('.post-page')
+ .should('exist')
+ .get('.hero-image > .image')
+ .should('not.exist')
+})
\ No newline at end of file
diff --git a/cypress/integration/Post.feature b/cypress/integration/Post.feature
new file mode 100644
index 000000000..7a572b955
--- /dev/null
+++ b/cypress/integration/Post.feature
@@ -0,0 +1,23 @@
+Feature: See a post
+ As an logged in user
+ I would like to see a post
+ And to see the whole content of it
+
+ Background:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | termsAndConditionsAgreedVersion |
+ | peter-pan| peter@pan.com | abcd | id-of-peter-pan| Peter Pan | 0.0.4 |
+ | narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
+ And the following "posts" are in the database:
+ | id | title | slug | authorId | content |
+ | aBcDeFgHiJ | previously created post | previously-created-post | id-of-peter-pan | with some content |
+ And I am logged in as "narrator"
+
+ Scenario: See a post on the newsfeed
+ When I navigate to page "/"
+ Then the post shows up on the newsfeed at position 1
+
+ Scenario: Navigate to the Post Page
+ When I navigate to page "/"
+ And I click on "the first post"
+ Then I am on page "/post/.*"
diff --git a/cypress/integration/Post/the_post_shows_up_on_the_newsfeed_at_position_{int}.js b/cypress/integration/Post/the_post_shows_up_on_the_newsfeed_at_position_{int}.js
new file mode 100644
index 000000000..3b42ea58e
--- /dev/null
+++ b/cypress/integration/Post/the_post_shows_up_on_the_newsfeed_at_position_{int}.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the post shows up on the newsfeed at position {int}", index => {
+ const selector = `.post-teaser:nth-child(${index}) > .base-card`;
+ cy.get(selector).should("contain", 'previously created post');
+ cy.get(selector).should("contain", 'with some content');
+
+});
\ No newline at end of file
diff --git a/cypress/integration/search/Search.feature b/cypress/integration/Search.feature.broken
similarity index 59%
rename from cypress/integration/search/Search.feature
rename to cypress/integration/Search.feature.broken
index d128838f3..a770c757c 100644
--- a/cypress/integration/search/Search.feature
+++ b/cypress/integration/Search.feature.broken
@@ -4,20 +4,21 @@ Feature: Search
In order to find related content
Background:
- Given I have a user account
- And we have the following posts in our database:
+ Given the following "users" are in the database:
+ | slug | email | password | id | name | termsAndConditionsAgreedVersion |
+ | narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
+ | search-for-me | u1@example.org | 1234 | user-for-search | Search for me | 0.0.4 |
+ | not-to-be-found | u2@example.org | 1234 | just-an-id | Not to be found | 0.0.4 |
+ And the following "posts" are in the database:
| id | title | content |
| p1 | 101 Essays that will change the way you think | 101 Essays, of course (PR)! |
- | p2 | No content | will be found in this post, I guarantee |
- And we have the following user accounts:
- | slug | name | id |
- | search-for-me | Search for me | user-for-search |
- | not-to-be-found | Not to be found | just-an-id |
-
- Given I am logged in
+ | p2 | No content | will be found in this post, I guarantee |
+ And I am logged in as "narrator"
+ And I navigate to page "/"
Scenario: Search for specific words
When I search for "Essays"
+ And I wait for 3000 milliseconds
Then I should have one item in the select dropdown
Then I should see the following posts in the select dropdown:
| title |
@@ -25,8 +26,9 @@ Feature: Search
Scenario: Press enter opens search page
When I type "PR" and press Enter
- Then I should see the search results page
- Then I should see the following posts on the search results page
+ Then I am on page "/search/search-results"
+ And the search parameter equals "?search=PR"
+ Then I should see the following posts on the search results page:
| title |
| 101 Essays that will change the way you think |
@@ -36,8 +38,9 @@ Feature: Search
Scenario: Select entry goes to post
When I search for "Essays"
+ And I wait for 3000 milliseconds
And I select a post entry
- Then I should be on the post's page
+ Then I am on page "/post/p1/101-essays-that-will-change-the-way-you-think"
Scenario: Select dropdown content
When I search for "Essays"
@@ -52,4 +55,4 @@ Feature: Search
| slug |
| search-for-me |
And I select a user entry
- Then I should be on the user's profile
\ No newline at end of file
+ Then I am on page "/profile/user-for-search/search-for-me"
\ No newline at end of file
diff --git a/cypress/integration/Search/I_select_a_post_entry.js b/cypress/integration/Search/I_select_a_post_entry.js
new file mode 100644
index 000000000..25611f91e
--- /dev/null
+++ b/cypress/integration/Search/I_select_a_post_entry.js
@@ -0,0 +1,7 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I select a post entry", () => {
+ cy.get(".searchable-input .search-post")
+ .first()
+ .trigger("click");
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/I_select_a_user_entry.js b/cypress/integration/Search/I_select_a_user_entry.js
new file mode 100644
index 000000000..b7222b2fb
--- /dev/null
+++ b/cypress/integration/Search/I_select_a_user_entry.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I select a user entry", () => {
+ cy.get(".searchable-input .user-teaser")
+ .first()
+ .trigger("click");
+})
\ No newline at end of file
diff --git a/cypress/integration/Search/I_should_have_one_item_in_the_select_dropdown.js b/cypress/integration/Search/I_should_have_one_item_in_the_select_dropdown.js
new file mode 100644
index 000000000..7e5188ab6
--- /dev/null
+++ b/cypress/integration/Search/I_should_have_one_item_in_the_select_dropdown.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should have one item in the select dropdown", () => {
+ cy.get(".searchable-input .ds-select-dropdown").should($li => {
+ expect($li).to.have.length(1);
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/I_should_not_see_posts_without_the_searched-for_term_in_the_select_dropdown.js b/cypress/integration/Search/I_should_not_see_posts_without_the_searched-for_term_in_the_select_dropdown.js
new file mode 100644
index 000000000..a76ed6a5d
--- /dev/null
+++ b/cypress/integration/Search/I_should_not_see_posts_without_the_searched-for_term_in_the_select_dropdown.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should not see posts without the searched-for term in the select dropdown", () => {
+ cy.get(".ds-select-dropdown")
+ .should("not.contain","No searched for content");
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/I_should_see_posts_with_the_searched-for_term_in_the_select_dropdown.js b/cypress/integration/Search/I_should_see_posts_with_the_searched-for_term_in_the_select_dropdown.js
new file mode 100644
index 000000000..ce755abb0
--- /dev/null
+++ b/cypress/integration/Search/I_should_see_posts_with_the_searched-for_term_in_the_select_dropdown.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see posts with the searched-for term in the select dropdown", () => {
+ cy.get(".ds-select-dropdown")
+ .should("contain","101 Essays that will change the way you think");
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/I_should_see_the_following_posts_on_the_search_results_page.js b/cypress/integration/Search/I_should_see_the_following_posts_on_the_search_results_page.js
new file mode 100644
index 000000000..f703a04f5
--- /dev/null
+++ b/cypress/integration/Search/I_should_see_the_following_posts_on_the_search_results_page.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see the following posts on the search results page:", table => {
+ table.hashes().forEach(({ title }) => {
+ cy.get(".post-teaser")
+ .should("contain",title)
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/I_should_see_the_following_users_in_the_select_dropdown.js b/cypress/integration/Search/I_should_see_the_following_users_in_the_select_dropdown.js
new file mode 100644
index 000000000..3e5e14043
--- /dev/null
+++ b/cypress/integration/Search/I_should_see_the_following_users_in_the_select_dropdown.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see the following users in the select dropdown:", table => {
+ cy.get(".search-heading").should("contain", "Users");
+ table.hashes().forEach(({ slug }) => {
+ cy.get(".ds-select-dropdown").should("contain", slug);
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/I_type_{string}_and_press_Enter.js b/cypress/integration/Search/I_type_{string}_and_press_Enter.js
new file mode 100644
index 000000000..1a0fc6d42
--- /dev/null
+++ b/cypress/integration/Search/I_type_{string}_and_press_Enter.js
@@ -0,0 +1,8 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I type {string} and press Enter", value => {
+ cy.get(".searchable-input .ds-select input")
+ .focus()
+ .type(value)
+ .type("{enter}", { force: true });
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/I_type_{string}_and_press_escape.js b/cypress/integration/Search/I_type_{string}_and_press_escape.js
new file mode 100644
index 000000000..a3cde6cda
--- /dev/null
+++ b/cypress/integration/Search/I_type_{string}_and_press_escape.js
@@ -0,0 +1,8 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I type {string} and press escape", value => {
+ cy.get(".searchable-input .ds-select input")
+ .focus()
+ .type(value)
+ .type("{esc}");
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/the_search_field_should_clear.js b/cypress/integration/Search/the_search_field_should_clear.js
new file mode 100644
index 000000000..f571cdbd4
--- /dev/null
+++ b/cypress/integration/Search/the_search_field_should_clear.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the search field should clear", () => {
+ cy.get(".searchable-input .ds-select input")
+ .should("have.text", "");
+});
\ No newline at end of file
diff --git a/cypress/integration/Search/the_search_parameter_equals_{string}.js b/cypress/integration/Search/the_search_parameter_equals_{string}.js
new file mode 100644
index 000000000..b8473584c
--- /dev/null
+++ b/cypress/integration/Search/the_search_parameter_equals_{string}.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the search parameter equals {string}", search => {
+ cy.location("search")
+ .should("eq", search);
+});
\ No newline at end of file
diff --git a/cypress/integration/User.Authentication.feature b/cypress/integration/User.Authentication.feature
new file mode 100644
index 000000000..db7680bd4
--- /dev/null
+++ b/cypress/integration/User.Authentication.feature
@@ -0,0 +1,26 @@
+Feature: User authentication
+ As an user
+ I want to sign in
+ In order to be able to posts and do other contributions as myself
+ Furthermore I want to be able to stay logged in and logout again
+
+ Background:
+ Given the following "users" are in the database:
+ | email | password | id | name | slug | termsAndConditionsAgreedVersion |
+ | peterpan@example.org | 1234 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
+
+ Scenario: Log in
+ When I navigate to page "/login"
+ And I fill in my credentials "peterpan@example.org" "1234"
+ And I click on "submit button"
+ Then I am logged in with username "Peter Pan"
+
+ Scenario: Refresh and stay logged in
+ Given I am logged in as "peter-pan"
+ When I refresh the page
+ Then I am logged in with username "Peter Pan"
+
+ Scenario: Log out
+ Given I am logged in as "peter-pan"
+ When I log out
+ Then I am on page "login"
diff --git a/cypress/integration/User.Authentication/I_am_logged_in_with_username_{string}.js b/cypress/integration/User.Authentication/I_am_logged_in_with_username_{string}.js
new file mode 100644
index 000000000..4383282bd
--- /dev/null
+++ b/cypress/integration/User.Authentication/I_am_logged_in_with_username_{string}.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I am logged in with username {string}", name => {
+ cy.get(".avatar-menu").click();
+ cy.get(".avatar-menu-popover").contains(name);
+ cy.get(".avatar-menu").click(); // Close menu again
+});
\ No newline at end of file
diff --git a/cypress/integration/user_profile/BlockUser.feature b/cypress/integration/User.Block.feature.broken
similarity index 57%
rename from cypress/integration/user_profile/BlockUser.feature
rename to cypress/integration/User.Block.feature.broken
index b5c510286..3d58c3c27 100644
--- a/cypress/integration/user_profile/BlockUser.feature
+++ b/cypress/integration/User.Block.feature.broken
@@ -1,16 +1,21 @@
-Feature: Block a User
+Feature: User - block an user
As a user
I'd like to have a button to block another user
To prevent him from seeing and interacting with my contributions
Background:
- Given I have a user account
- And there is an annoying user called "Harassing User"
- And I am logged in
+ Given the following "users" are in the database:
+ | email | password | id | name | slug | termsAndConditionsAgreedVersion |
+ | peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
+ | user@example.org | 123 | harassing-user | Harassing User | harassing-user | 0.0.4 |
+ And the following "posts" are in the database:
+ | id | title | slug | authorId |
+ | bWBjpkTKZp | previously created post | previously-created-post | id-of-peter-pan |
+ And I am logged in as "peter-pan"
Scenario: Block a user
- Given I am on the profile page of the annoying user
- When I click on "Block user" from the content menu in the user info box
+ When I navigate to page "profile/harassing-user"
+ And I click on "Block user" from the content menu in the user info box
And I "should" see "Unblock user" from the content menu in the user info box
And I navigate to my "Blocked users" settings page
Then I can see the following table:
@@ -19,42 +24,46 @@ Feature: Block a User
Scenario: Blocked user cannot interact with my contributions
Given I block the user "Harassing User"
- And I previously created a post
- And a blocked user visits the post page of one of my authored posts
+ And I am logged in as "harassing-user"
+ And I navigate to page "/post/previously-created-post"
Then they should see a text explaining why commenting is not possible
And they should not see the comment form
Scenario: Block a previously followed user
Given I follow the user "Harassing User"
- When I visit the profile page of the annoying user
+ When I navigate to page "/profile/harassing-user"
And I click on "Block user" from the content menu in the user info box
And I get removed from his follower collection
And I "should" see "Unblock user" from the content menu in the user info box
Scenario: Posts of blocked users are not filtered from search results
- Given "Harassing User" wrote a post "You can still see my posts"
+ Given "harassing-user" wrote a post "You can still see my posts"
And I block the user "Harassing User"
When I search for "see"
+ And I wait for 3000 milliseconds
Then I should see the following posts in the select dropdown:
| title |
| You can still see my posts |
Scenario: Blocked users can still see my posts
- Given I previously created a post
- And I block the user "Harassing User"
- And the "blocked" user searches for "previously created"
+ When I block the user "Harassing User"
+ And I am logged in as "harassing-user"
+ And I navigate to page "/"
+ And I search for "previously created"
+ And I wait for 3000 milliseconds
Then I should see the following posts in the select dropdown:
| title |
| previously created post |
Scenario: Blocked users cannot see they are blocked in their list
Given a user has blocked me
+ And I navigate to page "/"
And I navigate to my "Blocked users" settings page
Then I should see no users in my blocked users list
Scenario: Blocked users should not see link or button to unblock, only blocking users
Given a user has blocked me
- When I visit the profile page of the annoying user
+ When I navigate to page "/profile/harassing-user"
And I should see the "Follow" button
And I should not see "Unblock user" button
And I "should not" see "Unblock user" from the content menu in the user info box
diff --git a/cypress/integration/User.Block/I_block_the_user_{string}.js b/cypress/integration/User.Block/I_block_the_user_{string}.js
new file mode 100644
index 000000000..cde1d96b9
--- /dev/null
+++ b/cypress/integration/User.Block/I_block_the_user_{string}.js
@@ -0,0 +1,11 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I block the user {string}", name => {
+ cy.neode()
+ .first("User", { name })
+ .then(blockedUser => {
+ cy.neode()
+ .first("User", {id: "id-of-peter-pan"})
+ .relateTo(blockedUser, "blocked");
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/User.Block/I_should_not_see_{string}_button.js b/cypress/integration/User.Block/I_should_not_see_{string}_button.js
new file mode 100644
index 000000000..5bf0b7a68
--- /dev/null
+++ b/cypress/integration/User.Block/I_should_not_see_{string}_button.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I should not see {string} button', button => {
+ cy.get('.base-card .action-buttons')
+ .should('have.length', 1)
+})
\ No newline at end of file
diff --git a/cypress/integration/User.Block/I_should_see_no_users_in_my_blocked_users_list.js b/cypress/integration/User.Block/I_should_see_no_users_in_my_blocked_users_list.js
new file mode 100644
index 000000000..11161ef2f
--- /dev/null
+++ b/cypress/integration/User.Block/I_should_see_no_users_in_my_blocked_users_list.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see no users in my blocked users list", () => {
+ cy.get('.ds-placeholder')
+ .should('contain', "So far, you have not blocked anybody.")
+})
\ No newline at end of file
diff --git a/cypress/integration/User.Block/I_should_see_the_{string}_button.js b/cypress/integration/User.Block/I_should_see_the_{string}_button.js
new file mode 100644
index 000000000..373800870
--- /dev/null
+++ b/cypress/integration/User.Block/I_should_see_the_{string}_button.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I should see the {string} button', button => {
+ cy.get('.base-card .action-buttons .base-button')
+ .should('contain', button)
+})
\ No newline at end of file
diff --git a/cypress/integration/User.Block/I_{string}_see_{string}_from_the_content_menu_in_the_user_info_box.js b/cypress/integration/User.Block/I_{string}_see_{string}_from_the_content_menu_in_the_user_info_box.js
new file mode 100644
index 000000000..0f44b5192
--- /dev/null
+++ b/cypress/integration/User.Block/I_{string}_see_{string}_from_the_content_menu_in_the_user_info_box.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I {string} see {string} from the content menu in the user info box", (condition, link) => {
+ cy.get(".user-content-menu .base-button").click()
+ cy.get(".popover .ds-menu-item-link")
+ .should(condition === 'should' ? 'contain' : 'not.contain', link)
+})
\ No newline at end of file
diff --git a/cypress/integration/User.Block/a_user_has_blocked_me.js b/cypress/integration/User.Block/a_user_has_blocked_me.js
new file mode 100644
index 000000000..d1703407f
--- /dev/null
+++ b/cypress/integration/User.Block/a_user_has_blocked_me.js
@@ -0,0 +1,15 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("a user has blocked me", () => {
+ cy.neode()
+ .first("User", {
+ name: "Peter Pan"
+ })
+ .then(blockedUser => {
+ cy.neode()
+ .first("User", {
+ name: 'Harassing User'
+ })
+ .relateTo(blockedUser, "blocked");
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/User.Block/they_should_not_see_the_comment_form.js b/cypress/integration/User.Block/they_should_not_see_the_comment_form.js
new file mode 100644
index 000000000..962934994
--- /dev/null
+++ b/cypress/integration/User.Block/they_should_not_see_the_comment_form.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("they should not see the comment form", () => {
+ cy.get(".base-card").children().should('not.have.class', 'comment-form')
+})
\ No newline at end of file
diff --git a/cypress/integration/User.Block/they_should_see_a_text_explaining_why_commenting_is_not_possible.js b/cypress/integration/User.Block/they_should_see_a_text_explaining_why_commenting_is_not_possible.js
new file mode 100644
index 000000000..d95f3229c
--- /dev/null
+++ b/cypress/integration/User.Block/they_should_see_a_text_explaining_why_commenting_is_not_possible.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("they should see a text explaining why commenting is not possible", () => {
+ cy.get('.ds-placeholder').should('contain', "Commenting is not possible at this time on this post.")
+})
\ No newline at end of file
diff --git a/cypress/integration/User.Mute.feature.broken b/cypress/integration/User.Mute.feature.broken
new file mode 100644
index 000000000..1390063f7
--- /dev/null
+++ b/cypress/integration/User.Mute.feature.broken
@@ -0,0 +1,60 @@
+Feature: Mute a User
+ As a user
+ I'd like to have a button to mute another user
+ To prevent him from seeing and interacting with my contributions
+
+ Background:
+ Given the following "users" are in the database:
+ | email | password | id | name | slug | termsAndConditionsAgreedVersion |
+ | peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
+ | user@example.org | 123 | annoying-user | Annoying User | annoying-user | 0.0.4 |
+ Given the following "posts" are in the database:
+ | id | title | content | authorId |
+ | im-not-muted | Post that should be seen | cause I'm not muted | id-of-peter-pan |
+ | bWBjpkTKZp | previously created post | previously-created-post | id-of-peter-pan |
+ And I am logged in as "peter-pan"
+
+ Scenario: Mute a user
+ Given I navigate to page "/profile/annoying-user"
+ When I click on "Mute user" from the content menu in the user info box
+ And I navigate to my "Muted users" settings page
+ Then I can see the following table:
+ | Avatar | Name |
+ | | Annoying User |
+
+ Scenario: Mute a previously followed user
+ Given I follow the user "Annoying User"
+ And "annoying-user" wrote a post "Spam Spam Spam"
+ When I navigate to page "/profile/annoying-user"
+ And I click on "Mute user" from the content menu in the user info box
+ Then the list of posts of this user is empty
+ And I get removed from his follower collection
+
+ Scenario: Posts of muted users are filtered from search results, users are not
+ Given "annoying-user" wrote a post "Spam Spam Spam"
+ When I search for "Spam"
+ And I wait for 3000 milliseconds
+ Then I should see the following posts in the select dropdown:
+ | title |
+ | Spam Spam Spam |
+ When I mute the user "Annoying User"
+ And I refresh the page
+ And I search for "Anno"
+ And I wait for 3000 milliseconds
+ Then the search should not contain posts by the annoying user
+ But the search should contain the annoying user
+ But I search for "not muted"
+ And I wait for 3000 milliseconds
+ Then I should see the following posts in the select dropdown:
+ | title |
+ | Post that should be seen |
+
+ Scenario: Muted users can still see my posts
+ And I mute the user "Annoying User"
+ And I am logged in as "annoying-user"
+ And I navigate to page "/"
+ And I search for "previously created"
+ And I wait for 3000 milliseconds
+ Then I should see the following posts in the select dropdown:
+ | title |
+ | previously created post |
diff --git a/cypress/integration/User.Mute/I_mute_the_user_{string}.js b/cypress/integration/User.Mute/I_mute_the_user_{string}.js
new file mode 100644
index 000000000..e0ed382cb
--- /dev/null
+++ b/cypress/integration/User.Mute/I_mute_the_user_{string}.js
@@ -0,0 +1,13 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I mute the user {string}", name => {
+ cy.neode()
+ .first("User", { name })
+ .then(mutedUser => {
+ cy.neode()
+ .first("User", {
+ name: "Peter Pan"
+ })
+ .relateTo(mutedUser, "muted");
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/User.Mute/the_list_of_posts_of_this_user_is_empty.js b/cypress/integration/User.Mute/the_list_of_posts_of_this_user_is_empty.js
new file mode 100644
index 000000000..038ca2168
--- /dev/null
+++ b/cypress/integration/User.Mute/the_list_of_posts_of_this_user_is_empty.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the list of posts of this user is empty", () => {
+ cy.get(".base-card").not(".post-link");
+ cy.get(".main-container").find(".ds-space.hc-empty");
+});
\ No newline at end of file
diff --git a/cypress/integration/User.Mute/the_search_should_contain_the_annoying_user.js b/cypress/integration/User.Mute/the_search_should_contain_the_annoying_user.js
new file mode 100644
index 000000000..d29eafc2f
--- /dev/null
+++ b/cypress/integration/User.Mute/the_search_should_contain_the_annoying_user.js
@@ -0,0 +1,13 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the search should contain the annoying user", () => {
+ cy.get(".searchable-input .ds-select-dropdown")
+ .should($li => {
+ expect($li).to.have.length(1);
+ })
+ cy.get(".ds-select-dropdown .user-teaser .slug")
+ .should("contain", '@annoying-user');
+ cy.get(".searchable-input .ds-select input")
+ .focus()
+ .type("{esc}");
+})
\ No newline at end of file
diff --git a/cypress/integration/User.Mute/the_search_should_not_contain_posts_by_the_annoying_user.js b/cypress/integration/User.Mute/the_search_should_not_contain_posts_by_the_annoying_user.js
new file mode 100644
index 000000000..a2f0a01d7
--- /dev/null
+++ b/cypress/integration/User.Mute/the_search_should_not_contain_posts_by_the_annoying_user.js
@@ -0,0 +1,10 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the search should not contain posts by the annoying user", () => {
+ cy.get(".searchable-input .ds-select-dropdown").should($li => {
+ expect($li).to.have.length(1);
+ })
+ cy.get(".ds-select-dropdown")
+ .should("not.have.class", '.search-post')
+ .should("not.contain", 'Spam')
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.Avatar.feature b/cypress/integration/UserProfile.Avatar.feature
new file mode 100644
index 000000000..abb3fea63
--- /dev/null
+++ b/cypress/integration/UserProfile.Avatar.feature
@@ -0,0 +1,20 @@
+Feature: User profile - Upload avatar image
+ As a user
+ I would like to be able to add an avatar image to my profile
+ So that I can personalize my profile
+
+ Background:
+ Given the following "users" are in the database:
+ | email | password | id | name | slug | termsAndConditionsAgreedVersion |
+ | peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
+ | user@example.org | 123 | user | User | user | 0.0.4 |
+ And I am logged in as "peter-pan"
+
+ Scenario: Change my UserProfile Image
+ And I navigate to page "/profile/peter-pan"
+ Then I should be able to change my profile picture
+
+ Scenario: Unable to change another user's avatar
+ Given I am logged in as "user"
+ And I navigate to page "/profile/peter-pan"
+ Then I cannot upload a picture
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.Avatar/I_cannot_upload_a_picture.js b/cypress/integration/UserProfile.Avatar/I_cannot_upload_a_picture.js
new file mode 100644
index 000000000..d20a181f2
--- /dev/null
+++ b/cypress/integration/UserProfile.Avatar/I_cannot_upload_a_picture.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I cannot upload a picture", () => {
+ cy.get(".base-card")
+ .children()
+ .should("not.have.id", "customdropzone")
+ .should("have.class", "user-avatar");
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.Avatar/I_should_be_able_to_change_my_profile_picture.js b/cypress/integration/UserProfile.Avatar/I_should_be_able_to_change_my_profile_picture.js
new file mode 100644
index 000000000..f92789ef8
--- /dev/null
+++ b/cypress/integration/UserProfile.Avatar/I_should_be_able_to_change_my_profile_picture.js
@@ -0,0 +1,17 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should be able to change my profile picture", () => {
+ const avatarUpload = "onourjourney.png";
+
+ cy.fixture(avatarUpload, "base64").then(fileContent => {
+ cy.get("#customdropzone").upload(
+ { fileContent, fileName: avatarUpload, mimeType: "image/png" },
+ { subjectType: "drag-n-drop", force: true }
+ );
+ });
+ cy.get(".profile-avatar img")
+ .should("have.attr", "src")
+ .and("contains", "onourjourney");
+ cy.contains(".iziToast-message", "Upload successful")
+ .should("have.length",1);
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.ChangePassword.feature b/cypress/integration/UserProfile.ChangePassword.feature
new file mode 100644
index 000000000..a7eec1cce
--- /dev/null
+++ b/cypress/integration/UserProfile.ChangePassword.feature
@@ -0,0 +1,55 @@
+Feature: User profile - change password
+ As a user
+ I want to change my password in my settings
+ For security, e.g. if I exposed my password by accident
+
+ Login via email and password is a well-known authentication procedure and you
+ can assure to the server that you are who you claim to be. Either if you
+ exposed your password by acccident and you want to invalidate the exposed
+ password or just out of an good habit, you want to change your password.
+
+ Background:
+ Given the following "users" are in the database:
+ | email | password | id | name | slug | termsAndConditionsAgreedVersion |
+ | peterpan@example.org | exposed | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
+ And I am logged in as "peter-pan"
+ And I navigate to page "/settings"
+ And I click on "security menu"
+
+ Scenario: Incorrect Old Password
+ When I fill the password form with:
+ | Your old password | incorrect |
+ | Your new password | secure |
+ | Confirm new password | secure |
+ And I submit the form
+ And I see a "failure toaster" message:
+ """
+ Old password is not correct
+ """
+
+ Scenario: Incorrect Password Repeat
+ When I fill the password form with:
+ | Your old password | exposed |
+ | Your new password | secure |
+ | Confirm new password | eruces |
+ And I cannot submit the form
+
+ Scenario: Change my password
+ Given I navigate to page "/settings"
+ And I click on "security menu"
+ When I fill the password form with:
+ | Your old password | exposed |
+ | Your new password | secure |
+ | Confirm new password | secure |
+ And I submit the form
+ And I see a "success toaster" message:
+ """
+ Password successfully changed!
+ """
+ And I log out
+ Then I fill in my credentials "peterpan@example.org" "exposed"
+ And I click on "submit button"
+ And I cannot login anymore
+ But I fill in my credentials "peterpan@example.org" "secure"
+ And I click on "submit button"
+ And I can login successfully
diff --git a/cypress/integration/UserProfile.ChangePassword/I_can_login_successfully.js b/cypress/integration/UserProfile.ChangePassword/I_can_login_successfully.js
new file mode 100644
index 000000000..d1a62cc4d
--- /dev/null
+++ b/cypress/integration/UserProfile.ChangePassword/I_can_login_successfully.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I can login successfully", () => {
+ // cy.reload();
+ cy.get(".iziToast-wrapper")
+ .should("contain", "You are logged in!");
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.ChangePassword/I_cannot_login_anymore.js b/cypress/integration/UserProfile.ChangePassword/I_cannot_login_anymore.js
new file mode 100644
index 000000000..ff381d891
--- /dev/null
+++ b/cypress/integration/UserProfile.ChangePassword/I_cannot_login_anymore.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I cannot login anymore", password => {
+ //cy.reload();
+ cy.get(".iziToast-wrapper")
+ .should("contain", "Incorrect email address or password.");
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.ChangePassword/I_cannot_submit_the_form.js b/cypress/integration/UserProfile.ChangePassword/I_cannot_submit_the_form.js
new file mode 100644
index 000000000..657d38bd8
--- /dev/null
+++ b/cypress/integration/UserProfile.ChangePassword/I_cannot_submit_the_form.js
@@ -0,0 +1,6 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I cannot submit the form", () => {
+ cy.get("button[type=submit]")
+ .should('be.disabled');
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.ChangePassword/I_fill_the_password_form_with.js b/cypress/integration/UserProfile.ChangePassword/I_fill_the_password_form_with.js
new file mode 100644
index 000000000..69345ecc6
--- /dev/null
+++ b/cypress/integration/UserProfile.ChangePassword/I_fill_the_password_form_with.js
@@ -0,0 +1,11 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I fill the password form with:", table => {
+ table = table.rowsHash();
+ cy.get("input[id=oldPassword]")
+ .type(table["Your old password"])
+ .get("input[id=password]")
+ .type(table["Your new password"])
+ .get("input[id=passwordConfirmation]")
+ .type(table["Confirm new password"]);
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.ChangePassword/I_see_a_{string}_message.js b/cypress/integration/UserProfile.ChangePassword/I_see_a_{string}_message.js
new file mode 100644
index 000000000..90ddf0bd3
--- /dev/null
+++ b/cypress/integration/UserProfile.ChangePassword/I_see_a_{string}_message.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I see a {string} message:", (type, message) => {
+ cy.contains(message);
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.ChangePassword/I_submit_the_form copy.js b/cypress/integration/UserProfile.ChangePassword/I_submit_the_form copy.js
new file mode 100644
index 000000000..18349cff8
--- /dev/null
+++ b/cypress/integration/UserProfile.ChangePassword/I_submit_the_form copy.js
@@ -0,0 +1,5 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I submit the form", () => {
+ cy.get("form").submit();
+});
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.NameDescriptionLocation.feature b/cypress/integration/UserProfile.NameDescriptionLocation.feature
new file mode 100644
index 000000000..891d98748
--- /dev/null
+++ b/cypress/integration/UserProfile.NameDescriptionLocation.feature
@@ -0,0 +1,38 @@
+Feature: User profile - name, description and location
+ As a user
+ I would like to change my name, add a description and a location
+ So others can see my name, get some info about me and my location
+
+ Background:
+ Given the following "users" are in the database:
+ | email | password | id | name | slug | termsAndConditionsAgreedVersion |
+ | peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
+ And I am logged in as "peter-pan"
+ And I navigate to page "settings"
+
+ Scenario: Change username
+ When I save "Hansi" as my new name
+ Then I can see my new name "Hansi" when I click on my profile picture in the top right
+ When I refresh the page
+ Then I can see my new name "Hansi" when I click on my profile picture in the top right
+
+ Scenario Outline: I set my location to ""
+ When I save "" as my location
+ And I navigate to page "/profile/peter-pan"
+ Then they can see "" in the info box below my avatar
+ Examples: Location
+ | location | type |
+ | Paris | City |
+ | Saxony-Anhalt | Region |
+ | Germany | Country |
+
+ Scenario: Display a description on profile page
+ Given I have the following self-description:
+ """
+ Ich lebe fettlos, fleischlos, fischlos dahin, fühle mich aber ganz wohl dabei
+ """
+ When I navigate to page "/profile/peter-pan"
+ Then they can see the following text in the info box below my avatar:
+ """
+ Ich lebe fettlos, fleischlos, fischlos dahin, fühle mich aber ganz wohl dabei
+ """
diff --git a/cypress/integration/UserProfile.NameDescriptionLocation/I_can_see_my_new_name_{string}_when_I_click_on_my_profile_picture_in_the_top_right.js b/cypress/integration/UserProfile.NameDescriptionLocation/I_can_see_my_new_name_{string}_when_I_click_on_my_profile_picture_in_the_top_right.js
new file mode 100644
index 000000000..b9e97a304
--- /dev/null
+++ b/cypress/integration/UserProfile.NameDescriptionLocation/I_can_see_my_new_name_{string}_when_I_click_on_my_profile_picture_in_the_top_right.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I can see my new name {string} when I click on my profile picture in the top right', name => {
+ cy.get('.avatar-menu').click() // open
+ cy.get('.avatar-menu-popover').contains(name)
+ cy.get('.avatar-menu').click() // close again
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.NameDescriptionLocation/I_have_the_following_self-description.js b/cypress/integration/UserProfile.NameDescriptionLocation/I_have_the_following_self-description.js
new file mode 100644
index 000000000..a1bc1c524
--- /dev/null
+++ b/cypress/integration/UserProfile.NameDescriptionLocation/I_have_the_following_self-description.js
@@ -0,0 +1,12 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I have the following self-description:', text => {
+ cy.get('textarea[id=about]')
+ .clear()
+ .type(text)
+ cy.get('[type=submit]')
+ .click()
+ .not('[disabled]')
+ cy.get('.iziToast-message')
+ .should('contain', 'Your data was successfully updated')
+ })
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.NameDescriptionLocation/I_save_{string}_as_my_location.js b/cypress/integration/UserProfile.NameDescriptionLocation/I_save_{string}_as_my_location.js
new file mode 100644
index 000000000..de5143b9f
--- /dev/null
+++ b/cypress/integration/UserProfile.NameDescriptionLocation/I_save_{string}_as_my_location.js
@@ -0,0 +1,13 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I save {string} as my location', location => {
+ cy.get('input[id=city]').type(location)
+ cy.get('.ds-select-option')
+ .contains(location)
+ .click()
+ cy.get('[type=submit]')
+ .click()
+ .not('[disabled]')
+ cy.get('.iziToast-message')
+ .should('contain', 'Your data was successfully updated')
+ })
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.NameDescriptionLocation/I_save_{string}_as_my_new_name.js b/cypress/integration/UserProfile.NameDescriptionLocation/I_save_{string}_as_my_new_name.js
new file mode 100644
index 000000000..22e26cbc5
--- /dev/null
+++ b/cypress/integration/UserProfile.NameDescriptionLocation/I_save_{string}_as_my_new_name.js
@@ -0,0 +1,12 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I save {string} as my new name', name => {
+ cy.get('input[id=name]')
+ .clear()
+ .type(name)
+ cy.get('[type=submit]')
+ .click()
+ .not('[disabled]')
+ cy.get('.iziToast-message')
+ .should('contain', 'Your data was successfully updated')
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.NameDescriptionLocation/they_can_see_the_following_text_in_the_info_box_below_my_avatar.js b/cypress/integration/UserProfile.NameDescriptionLocation/they_can_see_the_following_text_in_the_info_box_below_my_avatar.js
new file mode 100644
index 000000000..6d375a406
--- /dev/null
+++ b/cypress/integration/UserProfile.NameDescriptionLocation/they_can_see_the_following_text_in_the_info_box_below_my_avatar.js
@@ -0,0 +1,5 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('they can see the following text in the info box below my avatar:', text => {
+ cy.contains(text)
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.NameDescriptionLocation/they_can_see_{string}_in_the_info_box_below_my_avatar.js b/cypress/integration/UserProfile.NameDescriptionLocation/they_can_see_{string}_in_the_info_box_below_my_avatar.js
new file mode 100644
index 000000000..ea328f441
--- /dev/null
+++ b/cypress/integration/UserProfile.NameDescriptionLocation/they_can_see_{string}_in_the_info_box_below_my_avatar.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('they can see {string} in the info box below my avatar', location => {
+ cy.contains(location)
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia.feature b/cypress/integration/UserProfile.SocialMedia.feature
new file mode 100644
index 000000000..5ab1feb25
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia.feature
@@ -0,0 +1,41 @@
+Feature: User profile - list social media accounts
+ As a User
+ I'd like to enter my social media
+ So I can show them to other users to get in contact
+
+ Background:
+ Given the following "users" are in the database:
+ | email | password | id | name | slug | termsAndConditionsAgreedVersion |
+ | peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
+ And I am logged in as "peter-pan"
+
+ Scenario: Adding Social Media
+ When I navigate to page "/settings/my-social-media"
+ Then I am on page "/settings/my-social-media"
+ When I add a social media link
+ Then I see a toaster with "Added social media"
+ And the new social media link shows up on the page
+
+ Scenario: Other users viewing my Social Media
+ Given I have added a social media link
+ When I navigate to page "/profile/peter-pan"
+ Then they should be able to see my social media links
+
+ Scenario: Deleting Social Media
+ When I navigate to page "/settings/my-social-media"
+ Then I am on page "/settings/my-social-media"
+ Given I have added a social media link
+ When I delete a social media link
+ Then I see a toaster with "Deleted social media"
+
+ Scenario: Editing Social Media
+ When I navigate to page "/settings/my-social-media"
+ Then I am on page "/settings/my-social-media"
+ Given I have added a social media link
+ When I start editing a social media link
+ Then I can cancel editing
+ When I start editing a social media link
+ And I edit and save the link
+ Then I see a toaster with "Added social media"
+ And the new url is displayed
+ But the old url is not displayed
diff --git a/cypress/integration/UserProfile.SocialMedia/I_add_a_social_media_link.js b/cypress/integration/UserProfile.SocialMedia/I_add_a_social_media_link.js
new file mode 100644
index 000000000..9253709f9
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/I_add_a_social_media_link.js
@@ -0,0 +1,9 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I add a social media link', () => {
+ cy.get('input#addSocialMedia')
+ .type('https://freeradical.zone/peter-pan')
+ .get('button')
+ .contains('Add link')
+ .click()
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/I_can_cancel_editing.js b/cypress/integration/UserProfile.SocialMedia/I_can_cancel_editing.js
new file mode 100644
index 000000000..03d60c44a
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/I_can_cancel_editing.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I can cancel editing', () => {
+ cy.get('button#cancel')
+ .click()
+ .get('input#editSocialMedia')
+ .should('have.length', 0)
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/I_delete_a_social_media_link.js b/cypress/integration/UserProfile.SocialMedia/I_delete_a_social_media_link.js
new file mode 100644
index 000000000..10daffca1
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/I_delete_a_social_media_link.js
@@ -0,0 +1,6 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I delete a social media link', () => {
+ cy.get(".base-button[title='Delete']")
+ .click()
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/I_edit_and_save_the_link.js b/cypress/integration/UserProfile.SocialMedia/I_edit_and_save_the_link.js
new file mode 100644
index 000000000..714e6b701
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/I_edit_and_save_the_link.js
@@ -0,0 +1,10 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I edit and save the link', () => {
+ cy.get('input#editSocialMedia')
+ .clear()
+ .type('https://freeradical.zone/tinkerbell')
+ .get('button')
+ .contains('Save')
+ .click()
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/I_have_added_a_social_media_link.js b/cypress/integration/UserProfile.SocialMedia/I_have_added_a_social_media_link.js
new file mode 100644
index 000000000..203b97032
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/I_have_added_a_social_media_link.js
@@ -0,0 +1,10 @@
+import { Given } from "cypress-cucumber-preprocessor/steps";
+
+Given('I have added a social media link', () => {
+ cy.visit('/settings/my-social-media')
+ .get('input#addSocialMedia')
+ .type('https://freeradical.zone/peter-pan')
+ .get('button')
+ .contains('Add link')
+ .click()
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/I_start_editing_a_social_media_link.js b/cypress/integration/UserProfile.SocialMedia/I_start_editing_a_social_media_link.js
new file mode 100644
index 000000000..1da05cfa5
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/I_start_editing_a_social_media_link.js
@@ -0,0 +1,6 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I start editing a social media link', () => {
+ cy.get(".base-button[title='Edit']")
+ .click()
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/the_new_social_media_link_shows_up_on_the_page.js b/cypress/integration/UserProfile.SocialMedia/the_new_social_media_link_shows_up_on_the_page.js
new file mode 100644
index 000000000..e72546f2a
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/the_new_social_media_link_shows_up_on_the_page.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('the new social media link shows up on the page', () => {
+ cy.get('a[href="https://freeradical.zone/peter-pan"]')
+ .should('have.length', 1)
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/the_new_url_is_displayed.js b/cypress/integration/UserProfile.SocialMedia/the_new_url_is_displayed.js
new file mode 100644
index 000000000..c25e6f0bb
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/the_new_url_is_displayed.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('the new url is displayed', () => {
+ cy.get("a[href='https://freeradical.zone/tinkerbell']")
+ .should('have.length', 1)
+})
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/the_old_url_is_not_displayed.js b/cypress/integration/UserProfile.SocialMedia/the_old_url_is_not_displayed.js
new file mode 100644
index 000000000..b3e804124
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/the_old_url_is_not_displayed.js
@@ -0,0 +1,7 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('the old url is not displayed', () => {
+ cy.get("a[href='https://freeradical.zone/peter-pan']")
+ .should('have.length', 0)
+})
+
\ No newline at end of file
diff --git a/cypress/integration/UserProfile.SocialMedia/they_should_be_able_to_see_my_social_media_links.js b/cypress/integration/UserProfile.SocialMedia/they_should_be_able_to_see_my_social_media_links.js
new file mode 100644
index 000000000..249e4f420
--- /dev/null
+++ b/cypress/integration/UserProfile.SocialMedia/they_should_be_able_to_see_my_social_media_links.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('they should be able to see my social media links', () => {
+ cy.get('.base-card')
+ .contains('Where else can I find Peter Pan?')
+ .get('a[href="https://freeradical.zone/peter-pan"]')
+ .should('have.length', 1)
+})
\ No newline at end of file
diff --git a/cypress/integration/administration/PinPost.feature b/cypress/integration/administration/PinPost.feature
deleted file mode 100644
index 40ff9cda5..000000000
--- a/cypress/integration/administration/PinPost.feature
+++ /dev/null
@@ -1,36 +0,0 @@
-Feature: Pin a post
- As an admin
- I want to pin a post so that it always appears at the top
- In order to make sure all network users read it - e.g. notify people about security incidents, maintenance downtimes
-
-
- Background:
- Given we have the following posts in our database:
- | id | title | pinned | createdAt |
- | p1 | Some other post | | 2020-01-21 |
- | p2 | Houston we have a problem | x | 2020-01-20 |
- | p3 | Yet another post | | 2020-01-19 |
-
- Scenario: Pinned post always appears on the top of the newsfeed
- Given I am logged in with a "user" role
- Then the first post on the landing page has the title:
- """
- Houston we have a problem
- """
- And the post with title "Houston we have a problem" has a ribbon for pinned posts
-
- Scenario: Ordinary users cannot pin a post
- Given I am logged in with a "user" role
- When I open the content menu of post "Yet another post"
- Then there is no button to pin a post
-
- Scenario: Admins are allowed to pin a post
- Given I am logged in with a "admin" role
- And I open the content menu of post "Yet another post"
- When I click on 'Pin post'
- Then I see a toaster with "Post pinned successfully"
- And the first post on the landing page has the title:
- """
- Yet another post
- """
- And the post with title "Yet another post" has a ribbon for pinned posts
diff --git a/cypress/integration/administration/TagsAndCategories.feature b/cypress/integration/administration/TagsAndCategories.feature
deleted file mode 100644
index 516966c6b..000000000
--- a/cypress/integration/administration/TagsAndCategories.feature
+++ /dev/null
@@ -1,36 +0,0 @@
-Feature: Tags and Categories
- As a database administrator
- I would like to see a summary of all tags and categories and their usage
- In order to be able to decide which tags and categories are popular or not
-
- The currently deployed application, codename "Alpha", distinguishes between
- categories and tags. Each post can have a number of categories and/or tags.
- A few categories are required for each post, tags are completely optional.
- Both help to find relevant posts in the database, e.g. users can filter for
- categories.
-
- If administrators summary of all tags and categories and how often they are
- used, they learn what new category might be convenient for users, e.g. by
- looking at the popularity of a tag.
-
- Background:
- Given I am logged in with a "admin" role
- And we have a selection of tags and categories as well as posts
-
- Scenario: See an overview of categories
- When I navigate to the administration dashboard
- And I click on the menu item "Categories"
- Then I can see the following table:
- | | Name | Posts |
- | | Just For Fun | 2 |
- | | Happiness & Values | 1 |
- | | Health & Wellbeing | 1 |
-
- Scenario: See an overview of tags
- When I navigate to the administration dashboard
- And I click on the menu item "Hashtags"
- Then I can see the following table:
- | No. | Hashtags | Users | Posts |
- | 1 | #Democracy | 3 | 4 |
- | 2 | #Nature | 2 | 3 |
- | 3 | #Ecology | 1 | 1 |
diff --git a/cypress/integration/common/I_am_logged_in_as_{string}.js b/cypress/integration/common/I_am_logged_in_as_{string}.js
new file mode 100644
index 000000000..96d1c28ab
--- /dev/null
+++ b/cypress/integration/common/I_am_logged_in_as_{string}.js
@@ -0,0 +1,18 @@
+import { Given } from "cypress-cucumber-preprocessor/steps";
+import encode from '../../../backend/src/jwt/encode'
+
+Given("I am logged in as {string}", slug => {
+ cy.neode()
+ .first("User", { slug })
+ .then(user => {
+ return new Cypress.Promise((resolve, reject) => {
+ if(!user) {
+ return reject(`User ${email} not found in database`)
+ }
+ return user.toJson().then((user) => resolve(user))
+ })
+ })
+ .then(user => {
+ cy.setCookie('ocelot-social-token', encode(user))
+ })
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_am_on_page_{string}.js b/cypress/integration/common/I_am_on_page_{string}.js
new file mode 100644
index 000000000..5ef1b9852
--- /dev/null
+++ b/cypress/integration/common/I_am_on_page_{string}.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I am on page {string}", page => {
+ cy.location("pathname")
+ .should("match", new RegExp(page));
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_can_see_the_following_table.js b/cypress/integration/common/I_can_see_the_following_table.js
new file mode 100644
index 000000000..9ebe1208c
--- /dev/null
+++ b/cypress/integration/common/I_can_see_the_following_table.js
@@ -0,0 +1,16 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then('I can see the following table:', table => {
+ const headers = table.raw()[0]
+ headers.forEach((expected, i) => {
+ cy.get('thead th')
+ .eq(i)
+ .should('contain', expected)
+ })
+ const flattened = [].concat.apply([], table.rows())
+ flattened.forEach((expected, i) => {
+ cy.get('tbody td')
+ .eq(i)
+ .should('contain', expected)
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/common/I_choose_the_following_text_as_content.js b/cypress/integration/common/I_choose_the_following_text_as_content.js
new file mode 100644
index 000000000..62b5426d5
--- /dev/null
+++ b/cypress/integration/common/I_choose_the_following_text_as_content.js
@@ -0,0 +1,9 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I choose the following text as content:", async text => {
+ cy.task('getValue', 'lastPost').then(lastPost => {
+ lastPost.content = text.replace("\n", " ");
+ cy.task('pushValue', { name: 'lastPost', value: lastPost })
+ cy.get(".editor .ProseMirror").type(lastPost.content);
+ })
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_click_on_{string}.js b/cypress/integration/common/I_click_on_{string}.js
new file mode 100644
index 000000000..5f43eb912
--- /dev/null
+++ b/cypress/integration/common/I_click_on_{string}.js
@@ -0,0 +1,19 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I click on {string}", element => {
+ const elementSelectors = {
+ 'submit button': 'button[name=submit]',
+ 'create post button': '.post-add-button',
+ 'save button': 'button[type=submit]',
+ 'the first post': '.post-teaser:first-child',
+ 'comment button': 'button[type=submit]',
+ 'reply button': '.reply-button',
+ 'security menu': 'a[href="/settings/security"]',
+ 'pin post': '.ds-menu-item:first-child',
+ 'Moderation': 'a[href="/moderation"]',
+ }
+
+ cy.get(elementSelectors[element])
+ .click()
+ .wait(750);
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_click_on_{string}_from_the_content_menu_in_the_user_info_box.js b/cypress/integration/common/I_click_on_{string}_from_the_content_menu_in_the_user_info_box.js
new file mode 100644
index 000000000..f1a859bfe
--- /dev/null
+++ b/cypress/integration/common/I_click_on_{string}_from_the_content_menu_in_the_user_info_box.js
@@ -0,0 +1,12 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I click on {string} from the content menu in the user info box",
+ button => {
+ cy.get(".user-content-menu .base-button").click();
+ cy.get(".popover .ds-menu-item-link")
+ .contains(button)
+ .click({
+ force: true
+ });
+ }
+);
\ No newline at end of file
diff --git a/cypress/integration/common/I_fill_in_my_credentials_{string}_{string}.js b/cypress/integration/common/I_fill_in_my_credentials_{string}_{string}.js
new file mode 100644
index 000000000..e2227f454
--- /dev/null
+++ b/cypress/integration/common/I_fill_in_my_credentials_{string}_{string}.js
@@ -0,0 +1,12 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I fill in my credentials {string} {string}", (email,password) => {
+ cy.get("input[name=email]")
+ .trigger("focus")
+ .type('{selectall}{backspace}')
+ .type(email)
+ .get("input[name=password]")
+ .trigger("focus")
+ .type('{selectall}{backspace}')
+ .type(password);
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_follow_the_user_{string}.js b/cypress/integration/common/I_follow_the_user_{string}.js
new file mode 100644
index 000000000..56d50a5ae
--- /dev/null
+++ b/cypress/integration/common/I_follow_the_user_{string}.js
@@ -0,0 +1,11 @@
+Given("I follow the user {string}", name => {
+ cy.neode()
+ .first("User", {name})
+ .then(followed => {
+ cy.neode()
+ .first("User", {
+ name: "Peter Pan"
+ })
+ .relateTo(followed, "following");
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_get_removed_from_his_follower_collection.js b/cypress/integration/common/I_get_removed_from_his_follower_collection.js
new file mode 100644
index 000000000..b32ca5961
--- /dev/null
+++ b/cypress/integration/common/I_get_removed_from_his_follower_collection.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I get removed from his follower collection", () => {
+ cy.get(".base-card")
+ .not(".post-link");
+ cy.get(".main-container")
+ .contains(".base-card","is not followed by anyone");
+ });
\ No newline at end of file
diff --git a/cypress/integration/common/I_log_out.js b/cypress/integration/common/I_log_out.js
new file mode 100644
index 000000000..51605f17e
--- /dev/null
+++ b/cypress/integration/common/I_log_out.js
@@ -0,0 +1,9 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I log out", () => {
+ cy.get(".avatar-menu")
+ .click();
+ cy.get(".avatar-menu-popover")
+ .find('a[href="/logout"]')
+ .click();
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_navigate_to_my_{string}_settings_page.js b/cypress/integration/common/I_navigate_to_my_{string}_settings_page.js
new file mode 100644
index 000000000..4d369eab2
--- /dev/null
+++ b/cypress/integration/common/I_navigate_to_my_{string}_settings_page.js
@@ -0,0 +1,10 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I navigate to my {string} settings page", settingsPage => {
+ cy.get(".avatar-menu-trigger").click();
+ cy.get(".avatar-menu-popover")
+ .find("a[href]")
+ .contains("Settings")
+ .click();
+ cy.contains(".ds-menu-item-link", settingsPage).click();
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_navigate_to_page_{string}.js b/cypress/integration/common/I_navigate_to_page_{string}.js
new file mode 100644
index 000000000..aa929c80a
--- /dev/null
+++ b/cypress/integration/common/I_navigate_to_page_{string}.js
@@ -0,0 +1,5 @@
+import { Given } from "cypress-cucumber-preprocessor/steps";
+
+Given("I navigate to page {string}", page => {
+ cy.visit(page);
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_refresh_the_page.js b/cypress/integration/common/I_refresh_the_page.js
new file mode 100644
index 000000000..1ac655cb4
--- /dev/null
+++ b/cypress/integration/common/I_refresh_the_page.js
@@ -0,0 +1,6 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When('I refresh the page', () => {
+ cy.visit('/')
+ .reload();
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_search_for_{string}.js b/cypress/integration/common/I_search_for_{string}.js
new file mode 100644
index 000000000..eaad481f7
--- /dev/null
+++ b/cypress/integration/common/I_search_for_{string}.js
@@ -0,0 +1,12 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I search for {string}", value => {
+ cy.intercept({
+ method: "POST",
+ url: "http://localhost:3000/api",
+ }).as("graphqlRequest");
+ cy.get(".searchable-input .ds-select input")
+ .focus()
+ .type(value);
+ cy.wait("@graphqlRequest");
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_see_a_toaster_with_{string}.js b/cypress/integration/common/I_see_a_toaster_with_{string}.js
new file mode 100644
index 000000000..1cf7da285
--- /dev/null
+++ b/cypress/integration/common/I_see_a_toaster_with_{string}.js
@@ -0,0 +1,5 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I see a toaster with {string}", (title) => {
+ cy.get(".iziToast-message").should("contain", title);
+})
\ No newline at end of file
diff --git a/cypress/integration/common/I_should_see_the_following_posts_in_the_select_dropdown.js b/cypress/integration/common/I_should_see_the_following_posts_in_the_select_dropdown.js
new file mode 100644
index 000000000..420c3376a
--- /dev/null
+++ b/cypress/integration/common/I_should_see_the_following_posts_in_the_select_dropdown.js
@@ -0,0 +1,8 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("I should see the following posts in the select dropdown:", table => {
+ table.hashes().forEach(({ title }) => {
+ cy.get(".ds-select-dropdown")
+ .should("contain", title);
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/common/I_wait_for_{int}_milliseconds.js b/cypress/integration/common/I_wait_for_{int}_milliseconds.js
new file mode 100644
index 000000000..bc8ef906a
--- /dev/null
+++ b/cypress/integration/common/I_wait_for_{int}_milliseconds.js
@@ -0,0 +1,5 @@
+import { When } from "cypress-cucumber-preprocessor/steps";
+
+When("I wait for {int} milliseconds", time => {
+ cy.wait(time)
+});
\ No newline at end of file
diff --git a/cypress/integration/common/admin.js b/cypress/integration/common/admin.js
deleted file mode 100644
index 346fe64fb..000000000
--- a/cypress/integration/common/admin.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { When, Then } from 'cypress-cucumber-preprocessor/steps'
-
-/* global cy */
-
-When('I navigate to the administration dashboard', () => {
- cy.get('.avatar-menu').click()
- cy.get('.avatar-menu-popover')
- .find('a[href="/admin"]')
- .click()
-})
-
-Then('I can see the following table:', table => {
- const headers = table.raw()[0]
- headers.forEach((expected, i) => {
- cy.get('thead th').eq(i).should('contain', expected)
- })
- const flattened = [].concat.apply([], table.rows())
- flattened.forEach((expected, i) => {
- cy.get('tbody td').eq(i).should('contain', expected)
- })
-})
diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js
deleted file mode 100644
index cba238a63..000000000
--- a/cypress/integration/common/post.js
+++ /dev/null
@@ -1,165 +0,0 @@
-import { When, Then } from "cypress-cucumber-preprocessor/steps";
-import locales from '../../../webapp/locales'
-import orderBy from 'lodash/orderBy'
-
-const languages = orderBy(locales, 'name')
-
-When("I type in a comment with {int} characters", size => {
- var c="";
- for (var i = 0; i < size; i++) {
- c += "c"
- }
- cy.get(".editor .ProseMirror").type(c);
-});
-
-Then("I click on the {string} button", text => {
- cy.get("button")
- .contains(text)
- .click();
-});
-
-Then("I click on the reply button", () => {
- cy.get(".reply-button")
- .click();
-});
-
-Then("my comment should be successfully created", () => {
- cy.get(".iziToast-message").contains("Comment submitted!");
-});
-
-Then("I should see my comment", () => {
- cy.get("article.comment-card p")
- .should("contain", "Human Connection rocks")
- .get(".user-teaser span.slug")
- .should("contain", "@peter-pan") // specific enough
- .get(".user-avatar img")
- .should("have.attr", "src")
- .and("contain", 'https://') // some url
- .get(".user-teaser > .info > .text")
- .should("contain", "today at");
-});
-
-Then("I should see the entirety of my comment", () => {
- cy.get("article.comment-card")
- .should("not.contain", "show more")
-});
-
-Then("I should see an abreviated version of my comment", () => {
- cy.get("article.comment-card")
- .should("contain", "show more")
-});
-
-Then("the editor should be cleared", () => {
- cy.get(".ProseMirror p").should("have.class", "is-empty");
-});
-
-Then("it should create a mention in the CommentForm", () => {
- cy.get(".ProseMirror a")
- .should('have.class', 'mention')
- .should('contain', '@peter-pan')
-})
-
-When("I open the content menu of post {string}", (title)=> {
- cy.contains('.post-teaser', title)
- .find('.content-menu .base-button')
- .click()
-})
-
-When("I click on 'Pin post'", (string)=> {
- cy.get("a.ds-menu-item-link").contains("Pin post")
- .click()
-})
-
-Then("there is no button to pin a post", () => {
- cy.get("a.ds-menu-item-link")
- .should('contain', "Report Post") // sanity check
- .should('not.contain', "Pin post")
-})
-
-And("the post with title {string} has a ribbon for pinned posts", (title) => {
- cy.get(".post-teaser").contains(title)
- .parent()
- .parent()
- .find(".ribbon.--pinned")
- .should("contain", "Announcement")
-})
-
-Then("I see a toaster with {string}", (title) => {
- cy.get(".iziToast-message").should("contain", title);
-})
-
-Then("I should be able to {string} a teaser image", condition => {
- cy.reload()
- const teaserImageUpload = (condition === 'change') ? "humanconnection.png" : "onourjourney.png";
- cy.fixture(teaserImageUpload).as('postTeaserImage').then(function() {
- cy.get("#postdropzone").upload(
- { fileContent: this.postTeaserImage, fileName: teaserImageUpload, mimeType: "image/png" },
- { subjectType: "drag-n-drop", force: true }
- );
- })
-})
-
-Then('confirm crop', () => {
- cy.get('.crop-confirm')
- .click()
-})
-
-Then("I add all required fields", () => {
- cy.get('input[name="title"]')
- .type('new post')
- .get(".editor .ProseMirror")
- .type('new post content')
- .get(".categories-select .base-button")
- .first()
- .click()
- .get('.base-card > .select-field input')
- .click()
- .get('.ds-select-option')
- .eq(languages.findIndex(l => l.code === 'en'))
- .click()
-})
-
-Then("the post was saved successfully with the {string} teaser image", condition => {
- cy.get(".base-card > .title")
- .should("contain", condition === 'updated' ? 'to be updated' : 'new post')
- .get(".content")
- .should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
- .get('.post-page img')
- .should("have.attr", "src")
- .and("contains", condition === 'updated' ? 'humanconnection' : 'onourjourney')
-})
-
-Then("the first image should not be displayed anymore", () => {
- cy.get(".hero-image")
- .children()
- .get('.hero-image > .image')
- .should('have.length', 1)
- .and('have.attr', 'src')
-})
-
-Then('the {string} post was saved successfully without a teaser image', condition => {
- cy.get(".base-card > .title")
- .should("contain", condition === 'updated' ? 'to be updated' : 'new post')
- .get(".content")
- .should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
- .get('.post-page')
- .should('exist')
- .get('.hero-image > .image')
- .should('not.exist')
-})
-
-Then('I should be able to remove it', () => {
- cy.get('.crop-cancel')
- .click()
-})
-
-When('my post has a teaser image', () => {
- cy.get('.contribution-form .image')
- .should('exist')
- .and('have.attr', 'src')
-})
-
-Then('I should be able to remove the image', () => {
- cy.get('.dz-message > .base-button')
- .click()
-})
diff --git a/cypress/integration/common/profile.js b/cypress/integration/common/profile.js
deleted file mode 100644
index a0be8a2cf..000000000
--- a/cypress/integration/common/profile.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { When, Then } from "cypress-cucumber-preprocessor/steps";
-
-/* global cy */
-
-When("I visit my profile page", () => {
- cy.openPage("profile/peter-pan");
-});
-
-Then("I should be able to change my profile picture", () => {
- const avatarUpload = "onourjourney.png";
-
- cy.fixture(avatarUpload, "base64").then(fileContent => {
- cy.get("#customdropzone").upload(
- { fileContent, fileName: avatarUpload, mimeType: "image/png" },
- { subjectType: "drag-n-drop", force: true }
- );
- });
- cy.get(".profile-avatar img")
- .should("have.attr", "src")
- .and("contains", "onourjourney");
- cy.contains(".iziToast-message", "Upload successful").should(
- "have.length",
- 1
- );
-});
-
-When("I visit another user's profile page", () => {
- cy.openPage("profile/peter-pan");
-});
-
-Then("I cannot upload a picture", () => {
- cy.get(".base-card")
- .children()
- .should("not.have.id", "customdropzone")
- .should("have.class", "user-avatar");
-});
diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js
deleted file mode 100644
index 4c6d2f6c3..000000000
--- a/cypress/integration/common/report.js
+++ /dev/null
@@ -1,182 +0,0 @@
-import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
-import { VERSION } from '../../constants/terms-and-conditions-version.js'
-import { gql } from '../../../backend/src/helpers/jest'
-
-/* global cy */
-
-let lastReportTitle
-let davidIrvingPostTitle = 'The Truth about the Holocaust'
-let davidIrvingPostSlug = 'the-truth-about-the-holocaust'
-let annoyingUserWhoMutedModeratorTitle = 'Fake news'
-
-const savePostTitle = $post => {
- return $post
- .first()
- .find('.title')
- .first()
- .invoke('text')
- .then(title => {
- lastReportTitle = title
- })
-}
-
-Given("I see David Irving's post on the landing page", page => {
- cy.openPage('landing')
-})
-
-Given("I see David Irving's post on the post page", page => {
- cy.visit(`/post/${davidIrvingPostSlug}`)
- cy.contains(davidIrvingPostTitle) // wait
-})
-
-Given('I am logged in with a {string} role', role => {
- cy.factory().build('user', {
- termsAndConditionsAgreedVersion: VERSION,
- role,
- name: `${role} is my name`
- }, {
- email: `${role}@example.org`,
- password: '1234',
- })
- cy.neode()
- .first("User", {
- name: `${role} is my name`,
- })
- .then(user => {
- return new Cypress.Promise((resolve, reject) => {
- return user.toJson().then((user) => resolve(user))
- })
- })
- .then(user => cy.login(user))
-})
-
-When('I click on "Report Post" from the content menu of the post', () => {
- cy.contains('.base-card', davidIrvingPostTitle)
- .find('.content-menu .base-button')
- .click({force: true})
-
- cy.get('.popover .ds-menu-item-link')
- .contains('Report Post')
- .click()
-})
-
-When('I click on "Report User" from the content menu in the user info box', () => {
- cy.contains('.base-card', davidIrvingPostTitle)
- .get('.user-content-menu .base-button')
- .click({ force: true })
-
- cy.get('.popover .ds-menu-item-link')
- .contains('Report User')
- .click()
-})
-
-When('I click on the author', () => {
- cy.get('.user-teaser')
- .click()
- .url().should('include', '/profile/')
-})
-
-When('I report the author', () => {
- cy.get('.page-name-profile-id-slug').then(() => {
- invokeReportOnElement('.base-card').then(() => {
- cy.get('button')
- .contains('Send')
- .click()
- })
- })
-})
-
-When('I click on send in the confirmation dialog', () => {
- cy.get('button')
- .contains('Send')
- .click()
-})
-
-Then('I get a success message', () => {
- cy.get('.iziToast-message').contains('Thanks')
-})
-
-Then('I see my reported user', () => {
- cy.get('table').then(() => {
- cy.get('tbody tr')
- .first()
- .contains(lastReportTitle.trim())
- })
-})
-
-Then(`I can't see the moderation menu item`, () => {
- cy.get('.avatar-menu-popover')
- .find('a[href="/settings"]', 'Settings')
- .should('exist') // OK, the dropdown is actually open
-
- cy.get('.avatar-menu-popover')
- .find('a[href="/moderation"]', 'Moderation')
- .should('not.exist')
-})
-
-When(/^I confirm the reporting dialog .*:$/, message => {
- cy.contains(message) // wait for element to become visible
- cy.get('.ds-modal').within(() => {
- cy.get('.ds-radio-option-label')
- .first()
- .click({
- force: true
- })
- cy.get('button')
- .contains('Report')
- .click()
- })
-})
-
-Given('somebody reported the following posts:', table => {
- table.hashes().forEach(({ submitterEmail, resourceId, reasonCategory, reasonDescription }) => {
- const submitter = {
- email: submitterEmail,
- password: '1234'
- }
- cy.factory()
- .build('user', {}, submitter)
- .authenticateAs(submitter)
- .mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
- fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
- reportId
- }
- }`, {
- resourceId,
- reasonCategory,
- reasonDescription
- })
- })
-})
-
-Then('I see all the reported posts including the one from above', () => {
- cy.get('table tbody').within(() => {
- cy.contains('tr', davidIrvingPostTitle)
- })
-})
-
-Then('I see all the reported posts including from the user who muted me', () => {
- cy.get('table tbody').within(() => {
- cy.contains('tr', annoyingUserWhoMutedModeratorTitle)
- })
-})
-
-Then('each list item links to the post page', () => {
- cy.contains(davidIrvingPostTitle).click()
- cy.location('pathname').should('contain', '/post')
-})
-
-Then('I can visit the post page', () => {
- cy.contains(annoyingUserWhoMutedModeratorTitle).click()
- cy.location('pathname').should('contain', '/post')
- .get('.base-card .title').should('contain', annoyingUserWhoMutedModeratorTitle)
-})
-
-When("they have a post someone has reported", () => {
- cy.factory()
- .build("post", {
- title,
- }, {
- authorId: 'annnoying-user',
- });
-})
diff --git a/cypress/integration/common/search.js b/cypress/integration/common/search.js
deleted file mode 100644
index 5eae20a22..000000000
--- a/cypress/integration/common/search.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import { When, Then } from "cypress-cucumber-preprocessor/steps";
-When("I search for {string}", value => {
- cy.get(".searchable-input .ds-select input")
- .focus()
- .type(value);
-});
-
-Then("I should have one item in the select dropdown", () => {
- cy.get(".searchable-input .ds-select-dropdown").should($li => {
- expect($li).to.have.length(1);
- });
-});
-
-Then("the search should not contain posts by the annoying user", () => {
- cy.get(".searchable-input .ds-select-dropdown").should($li => {
- expect($li).to.have.length(1);
- })
- cy.get(".ds-select-dropdown")
- .should("not.have.class", '.search-post')
- .should("not.contain", 'Spam')
-});
-
-Then("the search should contain the annoying user", () => {
- cy.get(".searchable-input .ds-select-dropdown").should($li => {
- expect($li).to.have.length(1);
- })
- cy.get(".ds-select-dropdown .user-teaser .slug").should("contain", '@spammy-spammer');
- cy.get(".searchable-input .ds-select input")
- .focus()
- .type("{esc}");
-})
-
-Then("I should see the following posts in the select dropdown:", table => {
- table.hashes().forEach(({ title }) => {
- cy.get(".ds-select-dropdown").should("contain", title);
- });
-});
-
-Then("I should see the following users in the select dropdown:", table => {
- cy.get(".search-heading").should("contain", "Users");
- table.hashes().forEach(({ slug }) => {
- cy.get(".ds-select-dropdown").should("contain", slug);
- });
-});
-
-When("I type {string} and press Enter", value => {
- cy.get(".searchable-input .ds-select input")
- .focus()
- .type(value)
- .type("{enter}", { force: true });
-});
-
-When("I type {string} and press escape", value => {
- cy.get(".searchable-input .ds-select input")
- .focus()
- .type(value)
- .type("{esc}");
-});
-
-Then("the search field should clear", () => {
- cy.get(".searchable-input .ds-select input").should("have.text", "");
-});
-
-When("I select a post entry", () => {
- cy.get(".searchable-input .search-post")
- .first()
- .trigger("click");
-});
-
-Then("I should be on the post's page", () => {
- cy.location("pathname").should("contain", "/post/");
- cy.location("pathname").should(
- "eq",
- "/post/p1/101-essays-that-will-change-the-way-you-think"
- );
-});
-
-Then(
- "I should see posts with the searched-for term in the select dropdown",
- () => {
- cy.get(".ds-select-dropdown").should(
- "contain",
- "101 Essays that will change the way you think"
- );
- }
-);
-
-Then("I should see the search results page", () => {
- cy.location("pathname").should(
- "eq",
- "/search/search-results"
- );
- cy.location("search").should(
- "eq",
- "?search=PR"
- );
-});
-
-Then("I should see the following posts on the search results page",
- () => {
- cy.get(".post-teaser").should(
- "contain",
- "101 Essays that will change the way you think"
- );
- }
-);
-
-Then(
- "I should not see posts without the searched-for term in the select dropdown",
- () => {
- cy.get(".ds-select-dropdown").should(
- "not.contain",
- "No searched for content"
- );
- }
-);
-
-Then("I select a user entry", () => {
- cy.get(".searchable-input .user-teaser")
- .first()
- .trigger("click");
-})
-
-Then("I should be on the user's profile", () => {
- cy.location("pathname").should("eq", "/profile/user-for-search/search-for-me")
-})
diff --git a/cypress/integration/common/settings.js b/cypress/integration/common/settings.js
deleted file mode 100644
index 3dcff141d..000000000
--- a/cypress/integration/common/settings.js
+++ /dev/null
@@ -1,160 +0,0 @@
-import { When, Then } from 'cypress-cucumber-preprocessor/steps'
-
-/* global cy */
-
-let aboutMeText
-let myLocation
-
-const matchNameInUserMenu = name => {
- cy.get('.avatar-menu').click() // open
- cy.get('.avatar-menu-popover').contains(name)
- cy.get('.avatar-menu').click() // close again
-}
-
-When('I save {string} as my new name', name => {
- cy.get('input[id=name]')
- .clear()
- .type(name)
- cy.get('[type=submit]')
- .click()
- .not('[disabled]')
- cy.get('.iziToast-message')
- .should('contain', 'Your data was successfully updated')
-})
-
-When('I save {string} as my location', location => {
- cy.get('input[id=city]').type(location)
- cy.get('.ds-select-option')
- .contains(location)
- .click()
- cy.get('[type=submit]')
- .click()
- .not('[disabled]')
- cy.get('.iziToast-message')
- .should('contain', 'Your data was successfully updated')
- myLocation = location
-})
-
-When('I have the following self-description:', text => {
- cy.get('textarea[id=bio]')
- .clear()
- .type(text)
- cy.get('[type=submit]')
- .click()
- .not('[disabled]')
- cy.get('.iziToast-message')
- .should('contain', 'Your data was successfully updated')
- aboutMeText = text
-})
-
-When('people visit my profile page', url => {
- cy.openPage('/profile/peter-pan')
-})
-
-
-When('they can see the text in the info box below my avatar', () => {
- cy.contains(aboutMeText)
-})
-
-Then('they can see the location in the info box below my avatar', () => {
- cy.contains(myLocation)
-})
-
-Then('the name {string} is still there', name => {
- matchNameInUserMenu(name)
-})
-
-Then(
- 'I can see my new name {string} when I click on my profile picture in the top right',
- name => matchNameInUserMenu(name)
-)
-
-When('I click on the {string} link', link => {
- cy.get('a')
- .contains(link)
- .click()
-})
-
-Then('I should be on the {string} page', page => {
- cy.location()
- .should(loc => {
- expect(loc.pathname).to.eq(page)
- })
- .get('h2')
- .should('contain', 'Social media')
-})
-
-When('I add a social media link', () => {
- cy.get('input#addSocialMedia')
- .type('https://freeradical.zone/peter-pan')
- .get('button')
- .contains('Add link')
- .click()
-})
-
-Then('it gets saved successfully', () => {
- cy.get('.iziToast-message')
- .should('contain', 'Added social media')
-})
-
-Then('the new social media link shows up on the page', () => {
- cy.get('a[href="https://freeradical.zone/peter-pan"]')
- .should('have.length', 1)
-})
-
-Given('I have added a social media link', () => {
- cy.openPage('/settings/my-social-media')
- .get('input#addSocialMedia')
- .type('https://freeradical.zone/peter-pan')
- .get('button')
- .contains('Add link')
- .click()
-})
-
-Then('they should be able to see my social media links', () => {
- cy.get('.base-card')
- .contains('Where else can I find Peter Pan?')
- .get('a[href="https://freeradical.zone/peter-pan"]')
- .should('have.length', 1)
-})
-
-When('I delete a social media link', () => {
- cy.get(".base-button[title='Delete']")
- .click()
-})
-
-Then('it gets deleted successfully', () => {
- cy.get('.iziToast-message')
- .should('contain', 'Deleted social media')
-})
-
-When('I start editing a social media link', () => {
- cy.get(".base-button[title='Edit']")
- .click()
-})
-
-Then('I can cancel editing', () => {
- cy.get('button#cancel')
- .click()
- .get('input#editSocialMedia')
- .should('have.length', 0)
-})
-
-When('I edit and save the link', () => {
- cy.get('input#editSocialMedia')
- .clear()
- .type('https://freeradical.zone/tinkerbell')
- .get('button')
- .contains('Save')
- .click()
-})
-
-Then('the new url is displayed', () => {
- cy.get("a[href='https://freeradical.zone/tinkerbell']")
- .should('have.length', 1)
-})
-
-Then('the old url is not displayed', () => {
- cy.get("a[href='https://freeradical.zone/peter-pan']")
- .should('have.length', 0)
-})
diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js
deleted file mode 100644
index 22a9d016e..000000000
--- a/cypress/integration/common/steps.js
+++ /dev/null
@@ -1,609 +0,0 @@
-import {
- Given,
- When,
- Then
-} from "cypress-cucumber-preprocessor/steps";
-import helpers from "../../support/helpers";
-import { VERSION } from '../../constants/terms-and-conditions-version.js'
-import locales from '../../../webapp/locales'
-import orderBy from 'lodash/orderBy'
-
-/* global cy */
-
-const languages = orderBy(locales, 'name')
-let lastPost = {};
-
-let loginCredentials = {
- email: "peterpan@example.org",
- password: "1234"
-};
-const termsAndConditionsAgreedVersion = {
- termsAndConditionsAgreedVersion: VERSION
-};
-const narratorParams = {
- id: 'id-of-peter-pan',
- name: "Peter Pan",
- slug: "peter-pan",
- ...termsAndConditionsAgreedVersion,
-};
-
-const annoyingParams = {
- email: "spammy-spammer@example.org",
- slug: 'spammy-spammer',
- password: "1234",
-};
-
-Given("I am logged in", () => {
- cy.neode()
- .first("User", {
- name: narratorParams.name
- })
- .then(user => {
- return new Cypress.Promise((resolve, reject) => {
- return user.toJson().then((user) => resolve(user))
- })
- })
- .then(user => cy.login(user))
-});
-
-Given("I log in as {string}", name => {
- cy.logout()
- cy.neode()
- .first("User", {
- name
- })
- .then(user => {
- return new Cypress.Promise((resolve, reject) => {
- return user.toJson().then((user) => resolve(user))
- })
- })
- .then(user => cy.login(user))
-})
-
-Given("the {string} user searches for {string}", (_, postTitle) => {
- cy.logout()
- cy.neode()
- .first("User", {
- id: "annoying-user"
- })
- .then(user => {
- return new Cypress.Promise((resolve, reject) => {
- return user.toJson().then((user) => resolve(user))
- })
- })
- .then(user => cy.login(user))
- cy.get(".searchable-input .ds-select input")
- .focus()
- .type(postTitle);
-});
-
-Given("we have a selection of categories", () => {
- cy.factory().build('category', { id: "cat0", slug: "just-for-fun" });
-});
-
-Given("we have a selection of tags and categories as well as posts", () => {
- cy.factory()
- .build('category', { id: 'cat12', name: "Just For Fun", icon: "smile", })
- .build('category', { id: 'cat121', name: "Happiness & Values", icon: "heart-o"})
- .build('category', { id: 'cat122', name: "Health & Wellbeing", icon: "medkit"})
- .build("tag", { id: "Ecology" })
- .build("tag", { id: "Nature" })
- .build("tag", { id: "Democracy" })
- .build("user", { id: 'a1' })
- .build("post", {}, {
- authorId: 'a1',
- tagIds: ["Ecology", "Nature", "Democracy"],
- categoryIds: ["cat12"]
- })
- .build("post", {}, {
- authorId: 'a1',
- tagIds: ["Nature", "Democracy"],
- categoryIds: ["cat121"]
- })
- .build("user", { id: 'a2' })
- .build("post", {}, {
- authorId: 'a2',
- tagIds: ['Nature', 'Democracy'],
- categoryIds: ["cat12"]
- })
- .build("post", {}, {
- tagIds: ['Democracy'],
- categoryIds: ["cat122"]
- })
-});
-
-Given("we have the following user accounts:", table => {
- table.hashes().forEach(params => {
- cy.factory().build("user", {
- ...params,
- ...termsAndConditionsAgreedVersion
- }, params);
- });
-});
-
-Given("I have a user account", () => {
- cy.factory().build("user", narratorParams, loginCredentials);
-});
-
-Given("my user account has the role {string}", role => {
- cy.factory().build("user", {
- role,
- ...termsAndConditionsAgreedVersion,
- }, loginCredentials);
-});
-
-When("I log out", cy.logout);
-
-When("I visit {string}", page => {
- cy.openPage(page);
-});
-
-When("I visit the {string} page", page => {
- cy.openPage(page);
-});
-
-When("a blocked user visits the post page of one of my authored posts", () => {
- cy.logout()
- cy.neode()
- .first("User", {
- name: 'Harassing User'
- })
- .then(user => {
- return new Cypress.Promise((resolve, reject) => {
- return user.toJson().then((user) => resolve(user))
- })
- })
- .then(user => cy.login(user))
- cy.openPage('post/previously-created-post')
-})
-
-Given("I am on the {string} page", page => {
- cy.openPage(page);
-});
-
-When("I fill in my email and password combination and click submit", () => {
- cy.manualLogin(loginCredentials);
-});
-
-When(/(?:when )?I refresh the page/, () => {
- cy.visit('/')
- .reload();
-});
-
-When("I log out through the menu in the top right corner", () => {
- cy.get(".avatar-menu").click();
- cy.get(".avatar-menu-popover")
- .find('a[href="/logout"]')
- .click();
-});
-
-Then("I can see my name {string} in the dropdown menu", () => {
- cy.get(".avatar-menu-popover").should("contain", narratorParams.name);
-});
-
-Then("I see the login screen again", () => {
- cy.location("pathname").should("contain", "/login");
-});
-
-Then("I can click on my profile picture in the top right corner", () => {
- cy.get(".avatar-menu").click();
- cy.get(".avatar-menu-popover");
-});
-
-Then("I am still logged in", () => {
- cy.get(".avatar-menu").click();
- cy.get(".avatar-menu-popover").contains(narratorParams.name);
-});
-
-When("I select {string} in the language menu", name => {
- cy.switchLanguage(name, true);
-});
-
-Given("I previously switched the language to {string}", name => {
- cy.switchLanguage(name, true);
-});
-
-Then("the whole user interface appears in {string}", name => {
- const {
- code
- } = helpers.getLangByName(name);
- cy.get(`html[lang=${code}]`);
- cy.getCookie("locale").should("have.property", "value", code);
-});
-
-Then("I see a button with the label {string}", label => {
- cy.contains("button", label);
-});
-
-When(`I click on {string}`, linkOrButton => {
- cy.contains(linkOrButton).click();
-});
-
-When(`I click on the menu item {string}`, linkOrButton => {
- cy.contains(".ds-menu-item", linkOrButton).click();
-});
-
-When("I press {string}", label => {
- cy.contains(label).click();
-});
-
-Given("we have the following comments in our database:", table => {
- table.hashes().forEach((attributesOrOptions, i) => {
- cy.factory().build("comment", {
- ...attributesOrOptions,
- }, {
- ...attributesOrOptions,
- });
- })
-});
-
-Given("we have the following posts in our database:", table => {
- table.hashes().forEach((attributesOrOptions, i) => {
- cy.factory().build("post", {
- ...attributesOrOptions,
- deleted: Boolean(attributesOrOptions.deleted),
- disabled: Boolean(attributesOrOptions.disabled),
- pinned: Boolean(attributesOrOptions.pinned),
- }, {
- ...attributesOrOptions,
- });
- })
-})
-
-Then("I see a success message:", message => {
- cy.contains(message);
-});
-
-When("I click on the avatar menu in the top right corner", () => {
- cy.get(".avatar-menu").click();
-});
-
-When(
- "I click on the big plus icon in the bottom right corner to create post",
- () => {
- cy.get(".post-add-button").click();
- cy.location("pathname").should('eq', '/post/create')
- }
-);
-
-Given("I previously created a post", () => {
- lastPost = {
- lastPost,
- title: "previously created post",
- content: "with some content",
- };
- cy.factory()
- .build("post", lastPost, {
- authorId: narratorParams.id
- });
-});
-
-When("I choose {string} as the title of the post", title => {
- lastPost.title = title.replace("\n", " ");
- cy.get('input[name="title"]').type(lastPost.title);
-});
-
-When("I type in the following text:", text => {
- lastPost.content = text.replace("\n", " ");
- cy.get(".editor .ProseMirror").type(lastPost.content);
-});
-
-Then("I select a category", () => {
- cy.get(".base-button")
- .contains("Just for Fun")
- .click();
-});
-
-When("I choose {string} as the language for the post", (languageCode) => {
- cy.get('.contribution-form .ds-select')
- .click().get('.ds-select-option')
- .eq(languages.findIndex(l => l.code === languageCode)).click()
-})
-
-Then("the post shows up on the landing page at position {int}", index => {
- cy.openPage("landing");
- const selector = `.post-teaser:nth-child(${index}) > .base-card`;
- cy.get(selector).should("contain", lastPost.title);
- cy.get(selector).should("contain", lastPost.content);
-});
-
-Then("I get redirected to {string}", route => {
- cy.location("pathname").should("contain", route.replace("...", ""));
-});
-
-Then("the post was saved successfully", () => {
- cy.get(".base-card > .title").should("contain", lastPost.title);
- cy.get(".content").should("contain", lastPost.content);
-});
-
-Then(/^I should see only ([0-9]+) posts? on the landing page/, postCount => {
- cy.get(".post-teaser").should("have.length", postCount);
-});
-
-Then("the first post on the landing page has the title:", title => {
- cy.get(".post-teaser:first").should("contain", title);
-});
-
-Then(
- "the page {string} returns a 404 error with a message:",
- (route, message) => {
- cy.request({
- url: route,
- failOnStatusCode: false
- })
- .its("status")
- .should("eq", 404);
- cy.visit(route, {
- failOnStatusCode: false
- });
- cy.get(".error-message").should("contain", message);
- }
-);
-
-Given("I am logged in with these credentials:", table => {
- loginCredentials = table.hashes()[0];
- cy.factory().build("user", {
- ...termsAndConditionsAgreedVersion,
- name: loginCredentials.email,
- }, loginCredentials);
- cy.neode()
- .first("User", {
- name: loginCredentials.email,
- })
- .then(user => {
- return new Cypress.Promise((resolve, reject) => {
- return user.toJson().then((user) => resolve(user))
- })
- })
- .then(user => cy.login(user))
-});
-
-When("I fill the password form with:", table => {
- table = table.rowsHash();
- cy.get("input[id=oldPassword]")
- .type(table["Your old password"])
- .get("input[id=password]")
- .type(table["Your new passsword"])
- .get("input[id=passwordConfirmation]")
- .type(table["Confirm new password"]);
-});
-
-When("submit the form", () => {
- cy.get("form").submit();
-});
-
-Then("I cannot login anymore with password {string}", password => {
- cy.reload();
- const { email } = loginCredentials
- cy.manualLogin({ email, password })
- .get(".iziToast-wrapper").should("contain", "Incorrect email address or password.");
-});
-
-Then("I can login successfully with password {string}", password => {
- cy.reload();
- const { email } = loginCredentials
- cy.manualLogin({ email, password })
- .get(".iziToast-wrapper").should("contain", "You are logged in!");
-});
-
-When("open the notification menu and click on the first item", () => {
- cy.get(".notifications-menu").invoke('show').click(); // "invoke('show')" because of the delay for show the menu
- cy.get(".notification .link")
- .first()
- .click({
- force: true
- });
-});
-
-Then("see {int} unread notifications in the top menu", count => {
- cy.get(".notifications-menu").should("contain", count);
-});
-
-Then("I get to the post page of {string}", path => {
- path = path.replace("...", "");
- cy.url().should("contain", "/post/");
- cy.url().should("contain", path);
-});
-
-When(
- "I start to write a new post with the title {string} beginning with:",
- (title, intro) => {
- cy.get(".post-add-button").click();
- cy.get('input[name="title"]').type(title);
- cy.get(".ProseMirror").type(intro);
- }
-);
-
-When("mention {string} in the text", mention => {
- cy.get(".ProseMirror").type(" @");
- cy.get(".suggestion-list__item")
- .contains(mention)
- .click();
-});
-
-Then("the unread counter is removed", () => {
- cy.get('.notifications-menu .counter-icon').should('not.exist');
-});
-
-Then("the notification menu button links to the all notifications page", () => {
- cy.get(".notifications-menu").click();
- cy.location("pathname").should("contain", "/notifications");
-});
-
-Given("there is an annoying user called {string}", name => {
- cy.factory().build("user", {
- id: "annoying-user",
- name,
- ...termsAndConditionsAgreedVersion,
- }, annoyingParams);
-});
-
-Given("there is an annoying user who has muted me", () => {
- cy.neode()
- .first("User", {
- role: 'moderator'
- })
- .then(mutedUser => {
- cy.neode()
- .first("User", {
- id: 'annoying-user'
- })
- .relateTo(mutedUser, "muted");
- });
-});
-
-Given("I am on the profile page of the annoying user", name => {
- cy.openPage("profile/annoying-user/spammy-spammer");
-});
-
-When("I visit the profile page of the annoying user", name => {
- cy.openPage("profile/annoying-user");
-});
-
-When("I ", name => {
- cy.openPage("profile/annoying-user");
-});
-
-When(
- "I click on {string} from the content menu in the user info box",
- button => {
- cy.get(".user-content-menu .base-button").click();
- cy.get(".popover .ds-menu-item-link")
- .contains(button)
- .click({
- force: true
- });
- }
-);
-
-When("I navigate to my {string} settings page", settingsPage => {
- cy.get(".avatar-menu-trigger").click();
- cy.get(".avatar-menu-popover")
- .find("a[href]")
- .contains("Settings")
- .click();
- cy.contains(".ds-menu-item-link", settingsPage).click();
-});
-
-Given("I follow the user {string}", name => {
- cy.neode()
- .first("User", {
- name
- })
- .then(followed => {
- cy.neode()
- .first("User", {
- name: narratorParams.name
- })
- .relateTo(followed, "following");
- });
-});
-
-Given('{string} wrote a post {string}', (_, title) => {
- cy.factory()
- .build("post", {
- title,
- }, {
- authorId: 'annoying-user',
- });
-});
-
-Then("the list of posts of this user is empty", () => {
- cy.get(".base-card").not(".post-link");
- cy.get(".main-container").find(".ds-space.hc-empty");
-});
-
-Then("I get removed from his follower collection", () => {
- cy.get(".base-card").not(".post-link");
- cy.get(".main-container").contains(
- ".base-card",
- "is not followed by anyone"
- );
-});
-
-Given("I wrote a post {string}", title => {
- cy.factory()
- .build("post", {
- title,
- }, {
- authorId: narratorParams.id,
- });
-});
-
-When("I mute the user {string}", name => {
- cy.neode()
- .first("User", {
- name
- })
- .then(mutedUser => {
- cy.neode()
- .first("User", {
- name: narratorParams.name
- })
- .relateTo(mutedUser, "muted");
- });
-});
-
-When("I block the user {string}", name => {
- cy.neode()
- .first("User", {
- name
- })
- .then(blockedUser => {
- cy.neode()
- .first("User", {
- id: narratorParams.id
- })
- .relateTo(blockedUser, "blocked");
- });
-});
-
-When("a user has blocked me", () => {
- cy.neode()
- .first("User", {
- name: narratorParams.name
- })
- .then(blockedUser => {
- cy.neode()
- .first("User", {
- name: 'Harassing User'
- })
- .relateTo(blockedUser, "blocked");
- });
-});
-
-Then("I see only one post with the title {string}", title => {
- cy.get(".main-container")
- .find(".post-link")
- .should("have.length", 1);
- cy.get(".main-container").contains(".post-link", title);
-});
-
-Then("they should not see the comment form", () => {
- cy.get(".base-card").children().should('not.have.class', 'comment-form')
-})
-
-Then("they should see a text explaining why commenting is not possible", () => {
- cy.get('.ds-placeholder').should('contain', "Commenting is not possible at this time on this post.")
-})
-
-Then("I should see no users in my blocked users list", () => {
- cy.get('.ds-placeholder')
- .should('contain', "So far, you have not blocked anybody.")
-})
-
-Then("I {string} see {string} from the content menu in the user info box", (condition, link) => {
- cy.get(".user-content-menu .base-button").click()
- cy.get(".popover .ds-menu-item-link")
- .should(condition === 'should' ? 'contain' : 'not.contain', link)
-})
-
-Then('I should not see {string} button', button => {
- cy.get('.base-card .action-buttons')
- .should('have.length', 1)
-})
-
-Then('I should see the {string} button', button => {
- cy.get('.base-card .action-buttons .base-button')
- .should('contain', button)
-})
diff --git a/cypress/integration/common/the_first_post_on_the_newsfeed_has_the_title.js b/cypress/integration/common/the_first_post_on_the_newsfeed_has_the_title.js
new file mode 100644
index 000000000..afe370e90
--- /dev/null
+++ b/cypress/integration/common/the_first_post_on_the_newsfeed_has_the_title.js
@@ -0,0 +1,6 @@
+import { Then } from "cypress-cucumber-preprocessor/steps";
+
+Then("the first post on the newsfeed has the title:", title => {
+ cy.get(".post-teaser:first")
+ .should("contain", title);
+});
\ No newline at end of file
diff --git a/cypress/integration/common/the_following_{string}_are_in_the_database.js b/cypress/integration/common/the_following_{string}_are_in_the_database.js
new file mode 100644
index 000000000..1d17ec686
--- /dev/null
+++ b/cypress/integration/common/the_following_{string}_are_in_the_database.js
@@ -0,0 +1,35 @@
+import { Given } from "cypress-cucumber-preprocessor/steps";
+
+Given("the following {string} are in the database:", (table,data) => {
+ switch(table){
+ case "posts":
+ data.hashes().forEach( entry => {
+ cy.factory().build("post", {
+ ...entry,
+ deleted: Boolean(entry.deleted),
+ disabled: Boolean(entry.disabled),
+ pinned: Boolean(entry.pinned),
+ },{
+ ...entry,
+ tagIds: entry.tagIds ? entry.tagIds.split(',').map(item => item.trim()) : [],
+ });
+ })
+ break
+ case "comments":
+ data.hashes().forEach( entry => {
+ cy.factory()
+ .build("comment",entry,entry);
+ })
+ break
+ case "users":
+ data.hashes().forEach( entry => {
+ cy.factory().build("user",entry,entry);
+ });
+ break
+ case "tags":
+ data.hashes().forEach( entry => {
+ cy.factory().build("tag", entry, entry)
+ });
+ break
+ }
+})
\ No newline at end of file
diff --git a/cypress/integration/common/{string}_wrote_a_post_{string}.js b/cypress/integration/common/{string}_wrote_a_post_{string}.js
new file mode 100644
index 000000000..42ac98028
--- /dev/null
+++ b/cypress/integration/common/{string}_wrote_a_post_{string}.js
@@ -0,0 +1,10 @@
+import { Given } from "cypress-cucumber-preprocessor/steps";
+
+Given('{string} wrote a post {string}', (author, title) => {
+ cy.factory()
+ .build("post", {
+ title,
+ }, {
+ authorId: author,
+ });
+});
\ No newline at end of file
diff --git a/cypress/integration/moderation/HidePosts.feature b/cypress/integration/moderation/HidePosts.feature
deleted file mode 100644
index bb82c7188..000000000
--- a/cypress/integration/moderation/HidePosts.feature
+++ /dev/null
@@ -1,26 +0,0 @@
-Feature: Hide Posts
- As the moderator team
- we'd like to be able to hide posts from the public
- to enforce our network's code of conduct and/or legal regulations
-
- Background:
- Given we have the following posts in our database:
- | id | title | deleted | disabled |
- | p1 | This post should be visible | | |
- | p2 | This post is disabled | | x |
- | p3 | This post is deleted | x | |
-
- Scenario: Disabled posts don't show up on the landing page
- Given I am logged in with a "user" role
- Then I should see only 1 post on the landing page
- And the first post on the landing page has the title:
- """
- This post should be visible
- """
-
- Scenario: Visiting a disabled post's page should return 404
- Given I am logged in with a "user" role
- Then the page "/post/this-post-is-disabled" returns a 404 error with a message:
- """
- This post could not be found
- """
diff --git a/cypress/integration/notifications/Mentions.feature b/cypress/integration/notifications/Mentions.feature
deleted file mode 100644
index 1cf265624..000000000
--- a/cypress/integration/notifications/Mentions.feature
+++ /dev/null
@@ -1,29 +0,0 @@
-Feature: Notification for a mention
- As a user
- I want to be notified if sb. mentions me in a post or comment
- In order join conversations about or related to me
-
- Background:
- Given we have a selection of categories
- And we have the following user accounts:
- | name | slug | email | password |
- | Wolle aus Hamburg | wolle-aus-hamburg | wolle@example.org | 1234 |
- | Matt Rider | matt-rider | matt@example.org | 4321 |
-
- Scenario: Mention another user, re-login as this user and see notifications
- Given I log in as "Wolle aus Hamburg"
- And I start to write a new post with the title "Hey Matt" beginning with:
- """
- Big shout to our fellow contributor
- """
- And mention "@matt-rider" in the text
- And I select a category
- And I choose "en" as the language for the post
- And I click on "Save"
- And I log in as "Matt Rider"
- And see 1 unread notifications in the top menu
- And open the notification menu and click on the first item
- Then I get to the post page of ".../hey-matt"
- And the unread counter is removed
- And the notification menu button links to the all notifications page
-
diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature
deleted file mode 100644
index da261726b..000000000
--- a/cypress/integration/post/Comment.feature
+++ /dev/null
@@ -1,46 +0,0 @@
-Feature: Post Comment
- As a user
- I want to comment on contributions of others
- To be able to express my thoughts and emotions about these, discuss, and add give further information.
-
- Background:
- Given I have a user account
- And we have the following posts in our database:
- | id | title | slug | authorId |
- | bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | id-of-peter-pan |
- And we have the following comments in our database:
- | postId | content | authorId |
- | bWBjpkTKZp | @peter-pan reply to me | id-of-peter-pan |
- And I am logged in
-
- Scenario: Comment creation
- Given I visit "post/bWBjpkTKZp/101-essays"
- And I type in the following text:
- """
- Human Connection rocks
- """
- And I click on the "Comment" button
- Then my comment should be successfully created
- And I should see my comment
- And the editor should be cleared
-
- Scenario: View medium length comments
- Given I visit "post/bWBjpkTKZp/101-essays"
- And I type in a comment with 305 characters
- And I click on the "Comment" button
- Then my comment should be successfully created
- And I should see the entirety of my comment
- And the editor should be cleared
-
- Scenario: View long comments
- Given I visit "post/bWBjpkTKZp/101-essays"
- And I type in a comment with 1205 characters
- And I click on the "Comment" button
- Then my comment should be successfully created
- And I should see an abreviated version of my comment
- And the editor should be cleared
-
- Scenario: Direct reply to Comment
- Given I visit "post/bWBjpkTKZp/101-essays"
- And I click on the reply button
- Then it should create a mention in the CommentForm
diff --git a/cypress/integration/post/DeleteImage.feature b/cypress/integration/post/DeleteImage.feature
deleted file mode 100644
index 07bfe43b1..000000000
--- a/cypress/integration/post/DeleteImage.feature
+++ /dev/null
@@ -1,19 +0,0 @@
-Feature: Delete Teaser Image
- As a user
- I would like to be able to remove an image I have previously added to my Post
- So that I have control over the content of my Post
-
- Background:
- Given I have a user account
- Given I am logged in
- Given we have the following posts in our database:
- | authorId | id | title | content |
- | id-of-peter-pan | p1 | Post to be updated | successfully updated |
-
- Scenario: Delete existing image
- Given I am on the 'post/edit/p1' page
- And my post has a teaser image
- Then I should be able to remove the image
- And I click on "Save"
- Then I get redirected to ".../post-to-be-updated"
- And the "updated" post was saved successfully without a teaser image
diff --git a/cypress/integration/post/ImageUploader.feature b/cypress/integration/post/ImageUploader.feature
deleted file mode 100644
index 1bbd80c78..000000000
--- a/cypress/integration/post/ImageUploader.feature
+++ /dev/null
@@ -1,47 +0,0 @@
-Feature: Upload Teaser Image
- As a user
- I would like to be able to add a teaser image to my Post
- So that I can personalize my posts
-
-
- Background:
- Given I have a user account
- Given I am logged in
- Given we have the following posts in our database:
- | authorId | id | title | content |
- | id-of-peter-pan | p1 | Post to be updated | successfully updated |
-
- Scenario: Create a Post with a Teaser Image
- When I click on the big plus icon in the bottom right corner to create post
- Then I should be able to "add" a teaser image
- And confirm crop
- And I add all required fields
- And I click on "Save"
- Then I get redirected to ".../new-post"
- And the post was saved successfully with the "new" teaser image
-
- Scenario: Update a Post to add an image
- Given I am on the 'post/edit/p1' page
- And I should be able to "change" a teaser image
- And confirm crop
- And I click on "Save"
- Then I see a toaster with "Saved!"
- And I get redirected to ".../post-to-be-updated"
- Then the post was saved successfully with the "updated" teaser image
-
- Scenario: Add image, then add a different image
- When I click on the big plus icon in the bottom right corner to create post
- Then I should be able to "add" a teaser image
- And confirm crop
- And I should be able to "change" a teaser image
- And confirm crop
- And the first image should not be displayed anymore
-
- Scenario: Add image, then delete it
- When I click on the big plus icon in the bottom right corner to create post
- Then I should be able to "add" a teaser image
- And I should be able to remove it
- And I add all required fields
- And I click on "Save"
- Then I get redirected to ".../new-post"
- And the "new" post was saved successfully without a teaser image
diff --git a/cypress/integration/post/PersistentLinks.feature b/cypress/integration/post/PersistentLinks.feature
deleted file mode 100644
index 5ea48ef6a..000000000
--- a/cypress/integration/post/PersistentLinks.feature
+++ /dev/null
@@ -1,41 +0,0 @@
-Feature: Persistent Links
- As a user
- I want all links to carry permanent information that identifies the linked resource
- In order to have persistent links even if a part of the URL might change
-
- | | Modifiable | Referenceable | Unique | Purpose |
- | -- | -- | -- | -- | -- |
- | ID | no | yes | yes | Identity, Traceability, Links |
- | Slug | yes | yes | yes | @-Mentions, SEO-friendly URL |
- | Name | yes | no | no | Search, self-description |
-
-
- Background:
- Given we have the following user accounts:
- | id | name | slug |
- | MHNqce98y1 | Stephen Hawking | thehawk |
- And we have the following posts in our database:
- | id | title | slug |
- | bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays |
- And I have a user account
- And I am logged in
-
- Scenario Outline: Link with slug only is valid and gets auto-completed
- When I visit ""
- Then I get redirected to ""
- Examples:
- | url | redirectUrl |
- | /profile/thehawk | /profile/MHNqce98y1/thehawk |
- | /post/101-essays | /post/bWBjpkTKZp/101-essays |
-
- Scenario: Link with id only will always point to the same user
- When I visit "/profile/MHNqce98y1"
- Then I get redirected to "/profile/MHNqce98y1/thehawk"
-
- Scenario Outline: ID takes precedence over slug
- When I visit ""
- Then I get redirected to ""
- Examples:
- | url | redirectUrl |
- | /profile/MHNqce98y1/stephen-hawking | /profile/MHNqce98y1/thehawk |
- | /post/bWBjpkTKZp/the-way-you-think | /post/bWBjpkTKZp/101-essays |
diff --git a/cypress/integration/post/WritePost.feature b/cypress/integration/post/WritePost.feature
deleted file mode 100644
index 0d74606ad..000000000
--- a/cypress/integration/post/WritePost.feature
+++ /dev/null
@@ -1,28 +0,0 @@
-Feature: Create a post
- As a user
- I would like to create a post
- To say something to everyone in the community
-
- Background:
- Given I have a user account
- And I am logged in
- And we have a selection of categories
- And I am on the "landing" page
-
- Scenario: Create a post
- When I click on the big plus icon in the bottom right corner to create post
- And I choose "My first post" as the title of the post
- And I type in the following text:
- """
- Human Connection is a free and open-source social network
- for active citizenship.
- """
- And I select a category
- And I choose "en" as the language for the post
- And I click on "Save"
- Then I get redirected to ".../my-first-post"
- And the post was saved successfully
-
- Scenario: See a post on the landing page
- Given I previously created a post
- Then the post shows up on the landing page at position 1
diff --git a/cypress/integration/user_account/ChangePassword.feature b/cypress/integration/user_account/ChangePassword.feature
deleted file mode 100644
index dbdf724f7..000000000
--- a/cypress/integration/user_account/ChangePassword.feature
+++ /dev/null
@@ -1,30 +0,0 @@
-Feature: Change password
- As a user
- I want to change my password in my settings
- For security, e.g. if I exposed my password by accident
-
- Login via email and password is a well-known authentication procedure and you
- can assure to the server that you are who you claim to be. Either if you
- exposed your password by acccident and you want to invalidate the exposed
- password or just out of an good habit, you want to change your password.
-
- Background:
- Given I am logged in with these credentials:
- | email | password |
- | user@example.org | exposed |
-
- Scenario: Change my password
- Given I am on the "settings" page
- And I click on "Security"
- When I fill the password form with:
- | Your old password | exposed |
- | Your new passsword | secure |
- | Confirm new password | secure |
- And submit the form
- And I see a success message:
- """
- Password successfully changed!
- """
- And I log out through the menu in the top right corner
- Then I cannot login anymore with password "exposed"
- But I can login successfully with password "secure"
diff --git a/cypress/integration/user_account/Login.feature b/cypress/integration/user_account/Login.feature
deleted file mode 100644
index 6e8f60a56..000000000
--- a/cypress/integration/user_account/Login.feature
+++ /dev/null
@@ -1,23 +0,0 @@
-Feature: Authentication
- As a database administrator
- I want users to sign in
- In order to attribute posts and other contributions to their authors
-
- Background:
- Given I have a user account
-
- Scenario: Log in
- When I visit the "login" page
- And I fill in my email and password combination and click submit
- Then I can click on my profile picture in the top right corner
- And I can see my name "Peter Lustig" in the dropdown menu
-
- Scenario: Refresh and stay logged in
- Given I am logged in
- When I refresh the page
- Then I am still logged in
-
- Scenario: Log out
- Given I am logged in
- When I log out through the menu in the top right corner
- Then I see the login screen again
diff --git a/cypress/integration/user_profile/AboutMeAndLocation.feature b/cypress/integration/user_profile/AboutMeAndLocation.feature
deleted file mode 100644
index 2a512bf3f..000000000
--- a/cypress/integration/user_profile/AboutMeAndLocation.feature
+++ /dev/null
@@ -1,37 +0,0 @@
-Feature: About me and location
- As a user
- I would like to add some about me text and a location
- So others can get some info about me and my location
-
- The location and about me are displayed on the user profile. Later it will be possible
- to search for users by location.
-
- Background:
- Given I have a user account
- And I am logged in
- And I am on the "settings" page
-
- Scenario: Change username
- When I save "Hansi" as my new name
- Then I can see my new name "Hansi" when I click on my profile picture in the top right
- And when I refresh the page
- Then the name "Hansi" is still there
-
- Scenario Outline: I set my location to ""
- When I save "" as my location
- When people visit my profile page
- Then they can see the location in the info box below my avatar
-
- Examples: Location
- | location | type |
- | Paris | City |
- | Saxony-Anhalt | Region |
- | Germany | Country |
-
- Scenario: Display a description on profile page
- Given I have the following self-description:
- """
- Ich lebe fettlos, fleischlos, fischlos dahin, fühle mich aber ganz wohl dabei
- """
- When people visit my profile page
- Then they can see the text in the info box below my avatar
diff --git a/cypress/integration/user_profile/SocialMedia.feature b/cypress/integration/user_profile/SocialMedia.feature
deleted file mode 100644
index e6090a0a4..000000000
--- a/cypress/integration/user_profile/SocialMedia.feature
+++ /dev/null
@@ -1,42 +0,0 @@
-Feature: List Social Media Accounts
- As a User
- I'd like to enter my social media
- So I can show them to other users to get in contact
-
- Background:
- Given I have a user account
- And I am logged in
-
- Scenario: Adding Social Media
- Given I am on the "settings" page
- And I click on the "Social media" link
- Then I should be on the "/settings/my-social-media" page
- When I add a social media link
- Then it gets saved successfully
- And the new social media link shows up on the page
-
- Scenario: Other users viewing my Social Media
- Given I have added a social media link
- When people visit my profile page
- Then they should be able to see my social media links
-
- Scenario: Deleting Social Media
- Given I am on the "settings" page
- And I click on the "Social media" link
- Then I should be on the "/settings/my-social-media" page
- Given I have added a social media link
- When I delete a social media link
- Then it gets deleted successfully
-
- Scenario: Editing Social Media
- Given I am on the "settings" page
- And I click on the "Social media" link
- Then I should be on the "/settings/my-social-media" page
- Given I have added a social media link
- When I start editing a social media link
- Then I can cancel editing
- When I start editing a social media link
- And I edit and save the link
- Then it gets saved successfully
- And the new url is displayed
- But the old url is not displayed
diff --git a/cypress/integration/user_profile/UploadUserProfileImage.feature b/cypress/integration/user_profile/UploadUserProfileImage.feature
deleted file mode 100644
index b46a31de8..000000000
--- a/cypress/integration/user_profile/UploadUserProfileImage.feature
+++ /dev/null
@@ -1,18 +0,0 @@
-Feature: Upload UserProfile Image
- As a user
- I would like to be able to add an avatar/profile pic to my profile
- So that I can personalize my profile
-
-
- Background:
- Given I have a user account
-
- Scenario: Change my UserProfile Image
- Given I am logged in
- And I visit my profile page
- Then I should be able to change my profile picture
-
- Scenario: Unable to change another user's avatar
- Given I am logged in with a "user" role
- And I visit another user's profile page
- Then I cannot upload a picture
\ No newline at end of file
diff --git a/cypress/integration/user_profile/mute-users/Mute.feature b/cypress/integration/user_profile/mute-users/Mute.feature
deleted file mode 100644
index 03ac4370b..000000000
--- a/cypress/integration/user_profile/mute-users/Mute.feature
+++ /dev/null
@@ -1,51 +0,0 @@
-Feature: Mute a User
- As a user
- I'd like to have a button to mute another user
- To prevent him from seeing and interacting with my contributions
- Background:
- Given I have a user account
- And there is an annoying user called "Spammy Spammer"
- And I am logged in
-
- Scenario: Mute a user
- Given I am on the profile page of the annoying user
- When I click on "Mute user" from the content menu in the user info box
- And I navigate to my "Muted users" settings page
- Then I can see the following table:
- | Avatar | Name |
- | | Spammy Spammer |
-
- Scenario: Mute a previously followed user
- Given I follow the user "Spammy Spammer"
- And "Spammy Spammer" wrote a post "Spam Spam Spam"
- When I visit the profile page of the annoying user
- And I click on "Mute user" from the content menu in the user info box
- Then the list of posts of this user is empty
- And I get removed from his follower collection
-
- Scenario: Posts of muted users are filtered from search results, users are not
- Given we have the following posts in our database:
- | id | title | content |
- | im-not-muted | Post that should be seen | cause I'm not muted |
- Given "Spammy Spammer" wrote a post "Spam Spam Spam"
- When I search for "Spam"
- Then I should see the following posts in the select dropdown:
- | title |
- | Spam Spam Spam |
- When I mute the user "Spammy Spammer"
- And I refresh the page
- And I search for "Spam"
- Then the search should not contain posts by the annoying user
- But the search should contain the annoying user
- But I search for "not muted"
- Then I should see the following posts in the select dropdown:
- | title |
- | Post that should be seen |
-
- Scenario: Muted users can still see my posts
- Given I previously created a post
- And I mute the user "Spammy Spammer"
- And the "muted" user searches for "previously created"
- Then I should see the following posts in the select dropdown:
- | title |
- | previously created post |
diff --git a/cypress/parallel-features.sh b/cypress/parallel-features.sh
new file mode 100755
index 000000000..a234b1d0e
--- /dev/null
+++ b/cypress/parallel-features.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Functions
+function join_by { local IFS="$1"; shift; echo "$*"; }
+
+# Arguments:
+CUR_JOB=$1
+MAX_JOBS=$2
+
+# Features
+FEATURE_LIST=( $(find cypress/integration/ -maxdepth 1 -name "*.feature") )
+
+# Calculation
+MAX_FEATURES=$(find cypress/integration/ -maxdepth 1 -name "*.feature" -printf '.' | wc -m)
+FEATURES_PER_JOB=$(expr $(expr ${MAX_FEATURES} + ${MAX_JOBS} - 1) / ${MAX_JOBS} )
+FEATURES_SKIP=$(expr $(expr ${CUR_JOB} - 1 ) \* ${FEATURES_PER_JOB} )
+
+# Comma separated list
+echo $(join_by , ${FEATURE_LIST[@]:${FEATURES_SKIP}:${FEATURES_PER_JOB}})
\ No newline at end of file
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
index cc6ac0e91..4e6b440ef 100644
--- a/cypress/plugins/index.js
+++ b/cypress/plugins/index.js
@@ -15,15 +15,27 @@
const cucumber = require('cypress-cucumber-preprocessor').default
const dotenv = require('dotenv')
+// Import backend .env (smart)?
+const { parsed } = dotenv.config({ path: require.resolve('../../backend/.env') })
+
+// Test persistent(between commands) store
+const testStore = {}
+
module.exports = (on, config) => {
- // (on, config) => {
- // `on` is used to hook into various events Cypress emits
- // `config` is the resolved Cypress config
- const { parsed } = dotenv.config({ path: require.resolve('../../backend/.env') })
config.env.NEO4J_URI = parsed.NEO4J_URI
config.env.NEO4J_USERNAME = parsed.NEO4J_USERNAME
config.env.NEO4J_PASSWORD = parsed.NEO4J_PASSWORD
config.env.JWT_SECRET = parsed.JWT_SECRET
on('file:preprocessor', cucumber())
+ on('task', {
+ pushValue({ name, value }) {
+ testStore[name] = value
+ return true
+ },
+ getValue(name) {
+ console.log("getValue",name,testStore)
+ return testStore[name]
+ },
+ })
return config
-}
+}
\ No newline at end of file
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index a15e57007..335e00390 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -14,16 +14,9 @@
/* globals Cypress cy */
import "cypress-file-upload";
-import helpers from "./helpers";
import { GraphQLClient, request } from 'graphql-request'
import { gql } from '../../backend/src/helpers/jest'
import config from '../../backend/src/config'
-import encode from '../../backend/src/jwt/encode'
-
-const switchLang = name => {
- cy.get(".locale-menu").click();
- cy.contains(".locale-menu-popover a", name).click();
-};
const authenticatedHeaders = (variables) => {
const mutation = gql`
@@ -38,50 +31,11 @@ const authenticatedHeaders = (variables) => {
})
}
-Cypress.Commands.add("switchLanguage", (name, force) => {
- const { code } = helpers.getLangByName(name);
- if (force) {
- switchLang(name);
- } else {
- cy.get("html").then($html => {
- if ($html && $html.attr("lang") !== code) {
- switchLang(name);
- }
- });
- }
-});
-
-Cypress.Commands.add("login", user => {
- const token = encode(user)
- cy.setCookie('human-connection-token', token)
- .visit("/")
-});
-
-Cypress.Commands.add("manualLogin", ({ email, password }) => {
- cy.visit(`/login`)
- .get("input[name=email]")
- .trigger("focus")
- .type(email)
- .get("input[name=password]")
- .trigger("focus")
- .type(password)
- .get("button[name=submit]")
- .as("submitButton")
- .click();
-});
-
Cypress.Commands.add("logout", () => {
cy.visit(`/logout`);
cy.location("pathname").should("contain", "/login"); // we're out
});
-Cypress.Commands.add("openPage", page => {
- if (page === "landing") {
- page = "";
- }
- cy.visit(`/${page}`);
-});
-
Cypress.Commands.add(
'authenticateAs',
({email, password}) => {
diff --git a/cypress/support/helpers.js b/cypress/support/helpers.js
deleted file mode 100644
index 7d66af5d6..000000000
--- a/cypress/support/helpers.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import find from 'lodash/find'
-import locales from '../../webapp/locales'
-
-export default {
- getLangByName(name) {
- return find(locales, { name })
- }
-}
diff --git a/cypress/support/index.js b/cypress/support/index.js
index 3290d2a5a..453c8476f 100644
--- a/cypress/support/index.js
+++ b/cypress/support/index.js
@@ -19,7 +19,7 @@ import './commands'
import './factories'
// intermittent failing tests
-import 'cypress-plugin-retries'
+// import 'cypress-plugin-retries'
// Alternatively you can use CommonJS syntax:
// require('./commands')
diff --git a/deployment/.gitignore b/deployment/.gitignore
deleted file mode 100644
index 61e591624..000000000
--- a/deployment/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-secrets.yaml
-configmap.yaml
-**/secrets.yaml
-**/configmap.yaml
-**/staging-values.yaml
-**/production-values.yaml
\ No newline at end of file
diff --git a/deployment/README.md b/deployment/README.md
deleted file mode 100644
index f3bb6d01e..000000000
--- a/deployment/README.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# ocelot.social \| Deployment Configuration
-
-There are a couple different ways we have tested to deploy an instance of ocelot.social, with [Kubernetes](https://kubernetes.io/) and via [Helm](https://helm.sh/docs/). In order to manage your own
-network, you have to [install Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/), [install Helm](https://helm.sh/docs/intro/install/) (optional, but the preferred way),
-and set up a Kubernetes cluster. Since there are many different options to host your cluster, we won't go into specifics here.
-
-We have tested two different Kubernetes providers: [Minikube](./minikube/README.md)
-and [Digital Ocean](./digital-ocean/README.md).
-
-Check out the specific documentation for your provider. After that, choose whether you want to go with the recommended deploy option [Helm](./helm/README.md), or use Kubernetes to apply the configuration for [ocelot.social](./ocelot-social/README.md).
-
-## Initialise Database For Production After Deployment
-
-After the first deployment of the new network on your server, the database must be initialized to start your network. This involves setting up a default administrator with the following data:
-
-- E-mail: admin@example.org
-- Password: 1234
-
-{% hint style="danger" %}
-TODO: When you are logged in for the first time, please change your (the admin's) e-mail to an existing one and change your password to a secure one !!!
-{% endhint %}
-
-Run the following command in the Docker container of the or a backend:
-
-{% tabs %}
-{% tab title="Kubernetes For Docker" %}
-
-```bash
-# with explicit pod backend name
-$ kubectl -n ocelot-social exec -it yarn prod:migrate init
-
-# or
-
-# if you have only one backend grep it
-$ kubectl -n ocelot-social exec -it $(kubectl -n ocelot-social get pods | grep backend | awk '{ print $1 }') yarn prod:migrate init
-
-# or
-
-# sh in your backend and do the command here
-$ kubectl -n ocelot-social exec -it $(kubectl -n ocelot-social get pods | grep backend | awk '{ print $1 }') sh
-backend: $ yarn prod:migrate init
-backend: $ exit
-```
-
-{% endtab %}
-{% tab title="Docker-Compose Running Local" %}
-
-```bash
-# exec in backend
-$ docker-compose exec backend yarn run db:migrate init
-```
-
-{% endtab %}
-{% tab title="Running Local" %}
-
-```bash
-# exec in folder backend/
-$ yarn run db:migrate init
-```
-
-{% endtab %}
-{% endtabs %}
diff --git a/deployment/digital-ocean/README.md b/deployment/digital-ocean/README.md
deleted file mode 100644
index 2ded38336..000000000
--- a/deployment/digital-ocean/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# Digital Ocean
-
-As a start, read the [introduction into Kubernetes](https://www.digitalocean.com/community/tutorials/an-introduction-to-kubernetes) by the folks at Digital Ocean. The following section should enable you to deploy ocelot.social to your Kubernetes cluster.
-
-## Connect to your local cluster
-
-1. Create a cluster at [Digital Ocean](https://www.digitalocean.com/).
-2. Download the `***-kubeconfig.yaml` from the Web UI.
-3. Move the file to the default location where kubectl expects it to be: `mv ***-kubeconfig.yaml ~/.kube/config`. Alternatively you can set the config on every command: `--kubeconfig ***-kubeconfig.yaml`
-4. Now check if you can connect to the cluster and if its your newly created one by running: `kubectl get nodes`
-
-The output should look about like this:
-
-```sh
-$ kubectl get nodes
-NAME STATUS ROLES AGE VERSION
-nifty-driscoll-uu1w Ready 69d v1.13.2
-nifty-driscoll-uuiw Ready 69d v1.13.2
-nifty-driscoll-uusn Ready 69d v1.13.2
-```
-
-If you got the steps right above and see your nodes you can continue.
-
-Digital Ocean Kubernetes clusters don't have a graphical interface, so I suggest
-to setup the [Kubernetes dashboard](./dashboard/README.md) as a next step.
-Configuring [HTTPS](./https/README.md) is bit tricky and therefore I suggest to
-do this as a last step.
-
-## Spaces
-
-We are storing our images in the s3-compatible [DigitalOcean Spaces](https://www.digitalocean.com/docs/spaces/).
-
-We still want to take backups of our images in case something happens to the images in the cloud. See these [instructions](https://www.digitalocean.com/docs/spaces/resources/s3cmd-usage/) about getting set up with `s3cmd` to take a copy of all images in a `Spaces` namespace, i.e. `ocelot-social-uploads`.
-
-After configuring `s3cmd` with your credentials, etc. you should be able to make a backup with this command.
-
-```sh
-s3cmg get --recursive --skip-existing s3://ocelot-social-uploads
-```
diff --git a/deployment/digital-ocean/dashboard/README.md b/deployment/digital-ocean/dashboard/README.md
deleted file mode 100644
index 5f66afe0b..000000000
--- a/deployment/digital-ocean/dashboard/README.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# Install Kubernetes Dashboard
-
-The kubernetes dashboard is optional but very helpful for debugging. If you want to install it, you have to do so only **once** per cluster:
-
-```bash
-# in folder deployment/digital-ocean/
-$ kubectl apply -f dashboard/
-$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml
-```
-
-### Login to your dashboard
-
-Proxy the remote kubernetes dashboard to localhost:
-
-```bash
-$ kubectl proxy
-```
-
-Visit:
-
-[http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/)
-
-You should see a login screen.
-
-To get your token for the dashboard you can run this command:
-
-```bash
-$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
-```
-
-It should print something like:
-
-```text
-Name: admin-user-token-6gl6l
-Namespace: kube-system
-Labels:
-Annotations: kubernetes.io/service-account.name=admin-user
- kubernetes.io/service-account.uid=b16afba9-dfec-11e7-bbb9-901b0e532516
-
-Type: kubernetes.io/service-account-token
-
-Data
-====
-ca.crt: 1025 bytes
-namespace: 11 bytes
-token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLTZnbDZsIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMTZhZmJhOS1kZmVjLTExZTctYmJiOS05MDFiMGU1MzI1MTYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.M70CU3lbu3PP4OjhFms8PVL5pQKj-jj4RNSLA4YmQfTXpPUuxqXjiTf094_Rzr0fgN_IVX6gC4fiNUL5ynx9KU-lkPfk0HnX8scxfJNzypL039mpGt0bbe1IXKSIRaq_9VW59Xz-yBUhycYcKPO9RM2Qa1Ax29nqNVko4vLn1_1wPqJ6XSq3GYI8anTzV8Fku4jasUwjrws6Cn6_sPEGmL54sq5R4Z5afUtv-mItTmqZZdxnkRqcJLlg2Y8WbCPogErbsaCDJoABQ7ppaqHetwfM_0yMun6ABOQbIwwl8pspJhpplKwyo700OSpvTT9zlBsu-b35lzXGBRHzv5g_RA
-```
-
-Grab the token from above and paste it into the [login screen](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/)
-
-When you are logged in, you should see sth. like:
-
-
-
-Feel free to save the login token from above in your password manager. Unlike the `kubeconfig` file, this token does not expire.
diff --git a/deployment/digital-ocean/dashboard/admin-user.yaml b/deployment/digital-ocean/dashboard/admin-user.yaml
deleted file mode 100644
index 27b6bb802..000000000
--- a/deployment/digital-ocean/dashboard/admin-user.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: v1
-kind: ServiceAccount
-metadata:
- name: admin-user
- namespace: kube-system
diff --git a/deployment/digital-ocean/dashboard/dashboard-screenshot.png b/deployment/digital-ocean/dashboard/dashboard-screenshot.png
deleted file mode 100644
index 6aefb5414..000000000
Binary files a/deployment/digital-ocean/dashboard/dashboard-screenshot.png and /dev/null differ
diff --git a/deployment/digital-ocean/dashboard/role-binding.yaml b/deployment/digital-ocean/dashboard/role-binding.yaml
deleted file mode 100644
index faa8927a2..000000000
--- a/deployment/digital-ocean/dashboard/role-binding.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
- name: admin-user
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: cluster-admin
-subjects:
-- kind: ServiceAccount
- name: admin-user
- namespace: kube-system
diff --git a/deployment/digital-ocean/https/.gitignore b/deployment/digital-ocean/https/.gitignore
deleted file mode 100644
index bebae8d05..000000000
--- a/deployment/digital-ocean/https/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-ingress.yaml
-issuer.yaml
diff --git a/deployment/digital-ocean/https/README.md b/deployment/digital-ocean/https/README.md
deleted file mode 100644
index 5729a763f..000000000
--- a/deployment/digital-ocean/https/README.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# Setup Ingress and HTTPS
-
-{% tabs %}
-{% tab title="Helm 3" %}
-
-## Via Helm 3
-
-Follow [this quick start guide](https://cert-manager.io/docs/) and install certmanager via Helm 3:
-
-## Or Via Kubernetes Directly
-
-```bash
-$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml
-```
-
-{% endtab %}
-{% tab title="Helm 2" %}
-
-{% hint style="info" %}
-CAUTION: Tiller on Helm 2 is [removed](https://helm.sh/docs/faq/#removal-of-tiller) on Helm 3, because of savety issues. So we recomment Helm 3.
-{% endhint %}
-
-Follow [this quick start guide](https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/index.html) and install certmanager via Helm 2 and tiller:
-[This resource was also helpful](https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html#installing-with-helm)
-
-```bash
-$ kubectl create serviceaccount tiller --namespace=kube-system
-$ kubectl create clusterrolebinding tiller-admin --serviceaccount=kube-system:tiller --clusterrole=cluster-admin
-$ helm init --service-account=tiller
-$ helm repo add jetstack https://charts.jetstack.io
-$ helm repo update
-$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml
-$ helm install --name cert-manager --namespace cert-manager --version v0.11.0 jetstack/cert-manager
-```
-
-{% endtab %}
-{% endtabs %}
-
-## Create Letsencrypt Issuers and Ingress Services
-
-Copy the configuration templates and change the file according to your needs.
-
-```bash
-# in folder deployment/digital-ocean/https/
-cp templates/issuer.template.yaml ./issuer.yaml
-cp templates/ingress.template.yaml ./ingress.yaml
-```
-
-At least, **change email addresses** in `issuer.yaml`. For sure you also want
-to _change the domain name_ in `ingress.yaml`.
-
-Once you are done, apply the configuration:
-
-```bash
-# in folder deployment/digital-ocean/https/
-$ kubectl apply -f .
-```
-
-{% hint style="info" %}
-CAUTION: It seems that the behaviour of Digital Ocean has changed and the load balancer is not created automatically anymore.
-And to create a load balancer costs money. Please refine the following documentation if required.
-{% endhint %}
-
-{% tabs %}
-{% tab title="Without Load Balancer" %}
-
-A solution without a load balance you can find [here](../no-loadbalancer/README.md).
-
-{% endtab %}
-{% tab title="With Digital Ocean Load Balancer" %}
-
-{% hint style="info" %}
-CAUTION: It seems that the behaviour of Digital Ocean has changed and the load balancer is not created automatically anymore.
-Please refine the following documentation if required.
-{% endhint %}
-
-In earlier days by now, your cluster should have a load balancer assigned with an external IP
-address. On Digital Ocean, this is how it should look like:
-
-
-
-If the load balancer isn't created automatically you have to create it your self on Digital Ocean under Networks.
-In case you don't need a Digital Ocean load balancer (which costs money by the way) have a look in the tab `Without Load Balancer`.
-
-{% endtab %}
-{% endtabs %}
-
-Check the ingress server is working correctly:
-
-```bash
-$ curl -kivL -H 'Host: ' 'https://'
-
-```
-
-If the response looks good, configure your domain registrar for the new IP address and the domain.
-
-Now let's get a valid HTTPS certificate. According to the tutorial above, check your tls certificate for staging:
-
-```bash
-$ kubectl -n ocelot-social describe certificate tls
-<
-...
-Spec:
- ...
- Issuer Ref:
- Group: cert-manager.io
- Kind: ClusterIssuer
- Name: letsencrypt-staging
-...
-Events:
-
->
-$ kubectl -n ocelot-social describe secret tls
-<
-...
-Annotations: ...
- cert-manager.io/issuer-kind: ClusterIssuer
- cert-manager.io/issuer-name: letsencrypt-staging
-...
->
-```
-
-If everything looks good, update the cluster-issuer of your ingress. Change the annotation `cert-manager.io/cluster-issuer` from `letsencrypt-staging` (for testing by getting a dummy certificate – no blocking by letsencrypt, because of to many request cycles) to `letsencrypt-prod` (for production with a real certificate – possible blocking by letsencrypt for several days, because of to many request cycles) in your ingress configuration in `ingress.yaml`.
-
-```bash
-# in folder deployment/digital-ocean/https/
-$ kubectl apply -f ingress.yaml
-```
-
-Take a minute and have a look if the certificate is now newly generated by `letsencrypt-prod`, the cluster-issuer for production:
-
-```bash
-$ kubectl -n ocelot-social describe certificate tls
-<
-...
-Spec:
- ...
- Issuer Ref:
- Group: cert-manager.io
- Kind: ClusterIssuer
- Name: letsencrypt-prod
-...
-Events:
-
->
-$ kubectl -n ocelot-social describe secret tls
-<
-...
-Annotations: ...
- cert-manager.io/issuer-kind: ClusterIssuer
- cert-manager.io/issuer-name: letsencrypt-prod
-...
->
-```
-
-In case the certificate is not newly created delete the former secret to force a refresh:
-
-```bash
-$ kubectl -n ocelot-social delete secret tls
-```
-
-Now, HTTPS should be configured on your domain. Congrats!
-
-For troubleshooting have a look at the cert-manager's [Troubleshooting](https://cert-manager.io/docs/faq/troubleshooting/) or [Troubleshooting Issuing ACME Certificates](https://cert-manager.io/docs/faq/acme/).
diff --git a/deployment/digital-ocean/https/ip-address.png b/deployment/digital-ocean/https/ip-address.png
deleted file mode 100644
index db523156a..000000000
Binary files a/deployment/digital-ocean/https/ip-address.png and /dev/null differ
diff --git a/deployment/digital-ocean/https/namespace.yaml b/deployment/digital-ocean/https/namespace.yaml
deleted file mode 100644
index 43898c546..000000000
--- a/deployment/digital-ocean/https/namespace.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-kind: Namespace
-apiVersion: v1
-metadata:
- name: ocelot-social
- labels:
- name: ocelot-social
diff --git a/deployment/digital-ocean/https/templates/ingress.template.yaml b/deployment/digital-ocean/https/templates/ingress.template.yaml
deleted file mode 100644
index 7fe690182..000000000
--- a/deployment/digital-ocean/https/templates/ingress.template.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-apiVersion: extensions/v1beta1
-kind: Ingress
-metadata:
- name: ingress
- namespace: ocelot-social
- annotations:
- kubernetes.io/ingress.class: "nginx"
- # cert-manager.io/issuer: "letsencrypt-staging" # in case using issuers instead of a cluster-issuers
- cert-manager.io/cluster-issuer: "letsencrypt-staging"
- nginx.ingress.kubernetes.io/proxy-body-size: 10m
-spec:
- rules:
- - host: develop-k8s.ocelot.social
- http:
- paths:
- - backend:
- serviceName: web
- servicePort: 3000
- path: /
- # decommt if you have installed the mailservice
- # - host: mail.ocelot.social
- # http:
- # paths:
- # - backend:
- # serviceName: mailserver
- # servicePort: 80
- # path: /
- # decommt to activate SSL via port 443 if you have installed the certificate. probalby via the cert-manager
- # tls:
- # - hosts:
- # - develop-k8s.ocelot.social
- # secretName: tls
diff --git a/deployment/digital-ocean/https/templates/issuer.template.yaml b/deployment/digital-ocean/https/templates/issuer.template.yaml
deleted file mode 100644
index a87b41438..000000000
--- a/deployment/digital-ocean/https/templates/issuer.template.yaml
+++ /dev/null
@@ -1,70 +0,0 @@
----
-# used while installation as first setup for testing purposes, recognize 'server: https://acme-staging-v02…'
-# !!! replace the e-mail for expiring certificates, see below !!!
-# !!! create the used secret, see below !!!
-apiVersion: cert-manager.io/v1
-kind: ClusterIssuer
-metadata:
- name: letsencrypt-staging
- namespace: ocelot-social
-spec:
- acme:
- # You must replace this email address with your own.
- # Let's Encrypt will use this to contact you about expiring
- # certificates, and issues related to your account.
- email: user@example.com
- server: https://acme-staging-v02.api.letsencrypt.org/directory
- privateKeySecretRef:
- # Secret resource that will be used to store the account's private key.
- name: letsencrypt-staging-issuer-account-key
- # Add a single challenge solver, HTTP01 using nginx
- solvers:
- - http01:
- ingress:
- class: nginx
----
-# used after installation for production, recognize 'server: https://acme-v02…'
-# !!! replace the e-mail for expiring certificates, see below !!!
-# !!! create the used secret, see below !!!
-apiVersion: cert-manager.io/v1
-kind: ClusterIssuer
-metadata:
- name: letsencrypt-prod
- namespace: ocelot-social
-spec:
- acme:
- # You must replace this email address with your own.
- # Let's Encrypt will use this to contact you about expiring
- # certificates, and issues related to your account.
- email: user@example.com
- server: https://acme-v02.api.letsencrypt.org/directory
- privateKeySecretRef:
- # Secret resource that will be used to store the account's private key.
- name: letsencrypt-prod-issuer-account-key
- # Add a single challenge solver, HTTP01 using nginx
- solvers:
- - http01:
- ingress:
- class: nginx
----
-# fill in your letsencrypt-staging-issuer-account-key
-# generate base 64: $ echo -n '' | base64
-apiVersion: v1
-data:
- tls.key:
-kind: Secret
-metadata:
- name: letsencrypt-staging-issuer-account-key
- namespace: ocelot-social
-type: Opaque
----
-# fill in your letsencrypt-prod-issuer-account-key
-# generate base 64: $ echo -n '' | base64
-apiVersion: v1
-data:
- tls.key:
-kind: Secret
-metadata:
- name: letsencrypt-prod-issuer-account-key
- namespace: ocelot-social
-type: Opaque
diff --git a/deployment/digital-ocean/no-loadbalancer/.gitignore b/deployment/digital-ocean/no-loadbalancer/.gitignore
deleted file mode 100644
index e44914af6..000000000
--- a/deployment/digital-ocean/no-loadbalancer/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-mydns.values.yaml
-myingress.values.yaml
diff --git a/deployment/digital-ocean/no-loadbalancer/README.md b/deployment/digital-ocean/no-loadbalancer/README.md
deleted file mode 100644
index 1afba0413..000000000
--- a/deployment/digital-ocean/no-loadbalancer/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Solution Withou A Loadbalancer
-
-## Expose Port 80 On Digital Ocean's Managed Kubernetes Without A Loadbalancer
-
-Follow [this solution](https://stackoverflow.com/questions/54119399/expose-port-80-on-digital-oceans-managed-kubernetes-without-a-load-balancer/55968709) and install a second firewall, nginx, and use external DNS via Helm 3.
-
-{% hint style="info" %}
-CAUTION: Some of the Helm charts are already depricated, so do an investigation of the approbriate charts and fill the correct commands in here.
-{% endhint %}
diff --git a/deployment/digital-ocean/no-loadbalancer/templates/mydns.values.template.yaml b/deployment/digital-ocean/no-loadbalancer/templates/mydns.values.template.yaml
deleted file mode 100644
index bfef4dd03..000000000
--- a/deployment/digital-ocean/no-loadbalancer/templates/mydns.values.template.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
----
-provider: digitalocean
-digitalocean:
- # create the API token at https://cloud.digitalocean.com/account/api/tokens
- # needs read + write
- apiToken: "DIGITALOCEAN_API_TOKEN"
-domainFilters:
- # domains you want external-dns to be able to edit
- - example.com
-rbac:
- create: true
\ No newline at end of file
diff --git a/deployment/digital-ocean/no-loadbalancer/templates/myingress.values.template.yaml b/deployment/digital-ocean/no-loadbalancer/templates/myingress.values.template.yaml
deleted file mode 100644
index f901a0677..000000000
--- a/deployment/digital-ocean/no-loadbalancer/templates/myingress.values.template.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
----
-controller:
- kind: DaemonSet
- hostNetwork: true
- dnsPolicy: ClusterFirstWithHostNet
- daemonset:
- useHostPort: true
- service:
- type: ClusterIP
-rbac:
- create: true
\ No newline at end of file
diff --git a/deployment/helm/ocelot.social/.helmignore b/deployment/helm/ocelot.social/.helmignore
deleted file mode 100644
index 50af03172..000000000
--- a/deployment/helm/ocelot.social/.helmignore
+++ /dev/null
@@ -1,22 +0,0 @@
-# Patterns to ignore when building packages.
-# This supports shell glob matching, relative path matching, and
-# negation (prefixed with !). Only one pattern per line.
-.DS_Store
-# Common VCS dirs
-.git/
-.gitignore
-.bzr/
-.bzrignore
-.hg/
-.hgignore
-.svn/
-# Common backup files
-*.swp
-*.bak
-*.tmp
-*~
-# Various IDEs
-.project
-.idea/
-*.tmproj
-.vscode/
diff --git a/deployment/helm/ocelot.social/Chart.yaml b/deployment/helm/ocelot.social/Chart.yaml
deleted file mode 100644
index bd67fde17..000000000
--- a/deployment/helm/ocelot.social/Chart.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: v1
-appVersion: "0.3.1"
-description: A Helm chart for ocelot.social
-name: ocelot-social
-version: 0.1.0
diff --git a/deployment/helm/ocelot.social/README.md b/deployment/helm/ocelot.social/README.md
deleted file mode 100644
index 3f964576e..000000000
--- a/deployment/helm/ocelot.social/README.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# Helm installation of Human Connection
-
-Deploying Human Connection with Helm is very straight forward. All you have to
-do is to change certain parameters, like domain names and API keys, then you
-just install our provided Helm chart to your cluster.
-
-## Configuration
-
-You can customize the network with your configuration by changing the `values.yaml`, all variables will be available as
-environment variables in your deployed kubernetes pods.
-
-Probably you want to change this environment variable to your actual domain:
-
-```bash
-# in folder /deployment/helm
-CLIENT_URI: "https://develop-k8s.ocelot.social"
-```
-
-If you want to edit secrets, you have to `base64` encode them. See [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret-manually). You can also use `helm-secrets`, but we have yet to test it.
-
-```bash
-# example how to base64 a string:
-$ echo -n 'admin' | base64
-YWRtaW4=
-```
-Those secrets get `base64` decoded and are available as environment variables in
-your deployed kubernetes pods.
-
-# https
-If you start with setting up the `https`, when you install the app, it will automatically take care of the certificates for you.
-
-First check that you are using `Helm v3`, this is important since it removes the need for `Tiller`. See, [FAQ](https://helm.sh/docs/faq/#removal-of-tiller)
-
-```bash
-$ helm version
-# output should look similar to this:
-#version.BuildInfo{Version:"v3.0.2", GitCommit:"19e47ee3283ae98139d98460de796c1be1e3975f", GitTreeState:"clean", GoVersion:"go1.13.5"}
-```
-
-Apply cert-manager CRDs before installing (or it will fail)
-
-```bash
-$ kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.13.0/deploy/manifests/00-crds.yaml
-```
-
-Next, create the `cert-manager` namespace
-```bash
-$ kubectl create namespace cert-manager
-```
-Add the `jetstack` repo and update
-
-```bash
-$ helm repo add jetstack https://charts.jetstack.io
-$ helm repo update
-```
-
-Install cert-manager
-```bash
-$ helm install cert-manager --namespace cert-manager --version v0.13.0 jetstack/cert-manager
-```
-
-# Deploy
-
-Once you are satisfied with the configuration, you can install the app.
-
-```bash
-# in folder /deployment/helm/human-connection
-$ helm install develop ./ --namespace human-connection
-```
-Where `develop` is the release name, in this case develop for our develop server and `human-connection` is the namespace, again customize for your needs. The release name can be anything you want. Just keep in mind that it is used in the templates to prepend the `CLIENT_URI` and other places.
-
-This will set up everything you need for the network, including `deployments`, and their `pods`, `services`, `ingress`, `volumes`(PersitentVolumes), `PersistentVolumeClaims`, and even `ClusterIssuers` for https certificates.
diff --git a/deployment/helm/ocelot.social/templates/cluster-issuers/letsencrypt-prod.yaml b/deployment/helm/ocelot.social/templates/cluster-issuers/letsencrypt-prod.yaml
deleted file mode 100644
index e46c1f0b3..000000000
--- a/deployment/helm/ocelot.social/templates/cluster-issuers/letsencrypt-prod.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: cert-manager.io/v1alpha2
-kind: ClusterIssuer
-metadata:
- name: letsencrypt-prod
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- acme:
- server: https://acme-v02.api.letsencrypt.org/directory
- email: {{ .Values.supportEmail }}
- privateKeySecretRef:
- name: letsencrypt-prod
- solvers:
- - http01:
- ingress:
- class: nginx
diff --git a/deployment/helm/ocelot.social/templates/cluster-issuers/letsencrypt-staging.yaml b/deployment/helm/ocelot.social/templates/cluster-issuers/letsencrypt-staging.yaml
deleted file mode 100644
index 531b2075b..000000000
--- a/deployment/helm/ocelot.social/templates/cluster-issuers/letsencrypt-staging.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: cert-manager.io/v1alpha2
-kind: ClusterIssuer
-metadata:
- name: letsencrypt-staging
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- acme:
- server: https://acme-staging-v02.api.letsencrypt.org/directory
- email: {{ .Values.supportEmail }}
- privateKeySecretRef:
- name: letsencrypt-staging
- solvers:
- - http01:
- ingress:
- class: nginx
diff --git a/deployment/helm/ocelot.social/templates/deployments/deployment-backend.yaml b/deployment/helm/ocelot.social/templates/deployments/deployment-backend.yaml
deleted file mode 100644
index bed4e0b77..000000000
--- a/deployment/helm/ocelot.social/templates/deployments/deployment-backend.yaml
+++ /dev/null
@@ -1,58 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: {{ .Release.Name }}-backend
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- replicas: 1
- minReadySeconds: 15
- progressDeadlineSeconds: 60
- strategy:
- rollingUpdate:
- maxSurge: 0
- maxUnavailable: "100%"
- selector:
- matchLabels:
- ocelot.social/selector: deployment-backend
- template:
- metadata:
- name: deployment-backend
- annotations:
- backup.velero.io/backup-volumes: uploads
- labels:
- ocelot.social/commit: {{ .Values.commit }}
- ocelot.social/selector: deployment-backend
- spec:
- containers:
- - name: backend
- image: "{{ .Values.backendImage }}:{{ .Chart.AppVersion }}"
- imagePullPolicy: {{ .Values.image.pullPolicy }}
- envFrom:
- - configMapRef:
- name: {{ .Release.Name }}-configmap
- - secretRef:
- name: {{ .Release.Name }}-secrets
- ports:
- - containerPort: 4000
- protocol: TCP
- resources: {}
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- volumeMounts:
- - mountPath: /develop-backend/public/uploads
- name: uploads
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- terminationGracePeriodSeconds: 30
- volumes:
- - name: uploads
- persistentVolumeClaim:
- claimName: uploads-claim
-status: {}
diff --git a/deployment/helm/ocelot.social/templates/deployments/deployment-mailserver.yaml b/deployment/helm/ocelot.social/templates/deployments/deployment-mailserver.yaml
deleted file mode 100644
index c0c0b70fc..000000000
--- a/deployment/helm/ocelot.social/templates/deployments/deployment-mailserver.yaml
+++ /dev/null
@@ -1,40 +0,0 @@
-{{- if .Values.developmentMailserverDomain }}
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: {{ .Release.Name }}-mailserver
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- replicas: 1
- minReadySeconds: 15
- progressDeadlineSeconds: 60
- selector:
- matchLabels:
- ocelot.social/selector: deployment-mailserver
- template:
- metadata:
- labels:
- ocelot.social/selector: deployment-mailserver
- name: mailserver
- spec:
- containers:
- - name: mailserver
- image: djfarrelly/maildev
- imagePullPolicy: {{ .Values.image.pullPolicy }}
- ports:
- - containerPort: 80
- - containerPort: 25
- envFrom:
- - configMapRef:
- name: {{ .Release.Name }}-configmap
- - secretRef:
- name: {{ .Release.Name }}-secrets
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
-status: {}
-{{- end}}
diff --git a/deployment/helm/ocelot.social/templates/deployments/deployment-maintenance.yaml b/deployment/helm/ocelot.social/templates/deployments/deployment-maintenance.yaml
deleted file mode 100644
index 2b33c1662..000000000
--- a/deployment/helm/ocelot.social/templates/deployments/deployment-maintenance.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: {{ .Release.Name }}-maintenance
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- selector:
- matchLabels:
- ocelot.social/selector: deployment-maintenance
- template:
- metadata:
- labels:
- ocelot.social/commit: {{ .Values.commit }}
- ocelot.social/selector: deployment-maintenance
- name: maintenance
- spec:
- containers:
- - name: maintenance
- env:
- - name: HOST
- value: 0.0.0.0
- image: "{{ .Values.maintenanceImage }}:{{ .Chart.AppVersion }}"
- ports:
- - containerPort: 80
- imagePullPolicy: Always
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
diff --git a/deployment/helm/ocelot.social/templates/deployments/deployment-neo4j.yaml b/deployment/helm/ocelot.social/templates/deployments/deployment-neo4j.yaml
deleted file mode 100644
index 3895bbf73..000000000
--- a/deployment/helm/ocelot.social/templates/deployments/deployment-neo4j.yaml
+++ /dev/null
@@ -1,52 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: {{ .Release.Name }}-neo4j
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- replicas: 1
- strategy:
- rollingUpdate:
- maxSurge: 0
- maxUnavailable: "100%"
- selector:
- matchLabels:
- ocelot.social/selector: deployment-neo4j
- template:
- metadata:
- name: neo4j
- annotations:
- backup.velero.io/backup-volumes: neo4j-data
- labels:
- ocelot.social/commit: {{ .Values.commit }}
- ocelot.social/selector: deployment-neo4j
- spec:
- containers:
- - name: neo4j
- image: "{{ .Values.neo4jImage }}:{{ .Chart.AppVersion }}"
- imagePullPolicy: {{ .Values.image.pullPolicy }}
- ports:
- - containerPort: 7687
- - containerPort: 7474
- resources:
- requests:
- memory: {{ .Values.neo4jResourceRequestsMemory | default "1G" | quote }}
- limits:
- memory: {{ .Values.neo4jResourceLimitsMemory | default "1G" | quote }}
- envFrom:
- - configMapRef:
- name: {{ .Release.Name }}-configmap
- volumeMounts:
- - mountPath: /data/
- name: neo4j-data
- volumes:
- - name: neo4j-data
- persistentVolumeClaim:
- claimName: neo4j-data-claim
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
diff --git a/deployment/helm/ocelot.social/templates/deployments/deployment-web.yaml b/deployment/helm/ocelot.social/templates/deployments/deployment-web.yaml
deleted file mode 100644
index 303a9fb43..000000000
--- a/deployment/helm/ocelot.social/templates/deployments/deployment-web.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: {{ .Release.Name }}-webapp
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- replicas: 2
- minReadySeconds: 15
- progressDeadlineSeconds: 60
- selector:
- matchLabels:
- ocelot.social/selector: deployment-webapp
- template:
- metadata:
- name: webapp
- labels:
- ocelot.social/commit: {{ .Values.commit }}
- ocelot.social/selector: deployment-webapp
- spec:
- containers:
- - name: webapp
- image: "{{ .Values.webappImage }}:{{ .Chart.AppVersion }}"
- imagePullPolicy: {{ .Values.image.pullPolicy }}
- envFrom:
- - configMapRef:
- name: {{ .Release.Name }}-configmap
- - secretRef:
- name: {{ .Release.Name }}-secrets
- env:
- - name: HOST
- value: 0.0.0.0
- ports:
- - containerPort: 3000
- resources: {}
- imagePullPolicy: Always
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
-status: {}
diff --git a/deployment/helm/ocelot.social/templates/ingress/ingress.template.yaml b/deployment/helm/ocelot.social/templates/ingress/ingress.template.yaml
deleted file mode 100644
index 0ef133f40..000000000
--- a/deployment/helm/ocelot.social/templates/ingress/ingress.template.yaml
+++ /dev/null
@@ -1,36 +0,0 @@
-apiVersion: extensions/v1beta1
-kind: Ingress
-metadata:
- name: {{ .Release.Name }}-ingress
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
- annotations:
- kubernetes.io/ingress.class: "nginx"
- cert-manager.io/cluster-issuer: {{ .Values.letsencryptIssuer }}
- nginx.ingress.kubernetes.io/proxy-body-size: 10m
-spec:
- tls:
- - hosts:
- - {{ .Values.domain }}
- secretName: tls
- rules:
- - host: {{ .Values.domain }}
- http:
- paths:
- - path: /
- backend:
- serviceName: {{ .Release.Name }}-webapp
- servicePort: 3000
-{{- if .Values.developmentMailserverDomain }}
- - host: {{ .Values.developmentMailserverDomain }}
- http:
- paths:
- - path: /
- backend:
- serviceName: {{ .Release.Name }}-mailserver
- servicePort: 80
-{{- end }}
diff --git a/deployment/helm/ocelot.social/templates/jobs/job-db-migration.yaml b/deployment/helm/ocelot.social/templates/jobs/job-db-migration.yaml
deleted file mode 100644
index e18ef77fa..000000000
--- a/deployment/helm/ocelot.social/templates/jobs/job-db-migration.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-apiVersion: batch/v1
-kind: Job
-metadata:
- name: {{ .Release.Name }}-db-migrations
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
- annotations:
- "helm.sh/hook": post-upgrade
- "helm.sh/hook-weight": "5"
- "helm.sh/hook-delete-policy": hook-succeeded, hook-failed
-spec:
- template:
- metadata:
- name: {{ .Release.Name }}
- spec:
- restartPolicy: Never
- containers:
- - name: db-migrations-job
- image: "{{ .Values.backendImage }}:latest"
- command: ["/bin/sh", "-c", "{{ .Values.dbMigrations }}"]
- envFrom:
- - configMapRef:
- name: {{ .Release.Name }}-configmap
- - secretRef:
- name: {{ .Release.Name }}-secrets
\ No newline at end of file
diff --git a/deployment/helm/ocelot.social/templates/services/service-backend.yaml b/deployment/helm/ocelot.social/templates/services/service-backend.yaml
deleted file mode 100644
index 8c1cc01d3..000000000
--- a/deployment/helm/ocelot.social/templates/services/service-backend.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: {{ .Release.Name }}-backend
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- ports:
- - name: graphql
- port: 4000
- targetPort: 4000
- selector:
- ocelot.social/selector: deployment-backend
diff --git a/deployment/helm/ocelot.social/templates/services/service-mailserver.yaml b/deployment/helm/ocelot.social/templates/services/service-mailserver.yaml
deleted file mode 100644
index b6b098123..000000000
--- a/deployment/helm/ocelot.social/templates/services/service-mailserver.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-{{- if .Values.developmentMailserverDomain }}
-apiVersion: v1
-kind: Service
-metadata:
- name: {{ .Release.Name }}-mailserver
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- ports:
- - name: web
- port: 80
- targetPort: 80
- - name: smtp
- port: 25
- targetPort: 25
- selector:
- ocelot.social/selector: deployment-mailserver
-{{- end}}
diff --git a/deployment/helm/ocelot.social/templates/services/service-maintenance.yaml b/deployment/helm/ocelot.social/templates/services/service-maintenance.yaml
deleted file mode 100644
index 0730f8027..000000000
--- a/deployment/helm/ocelot.social/templates/services/service-maintenance.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: {{ .Release.Name }}-maintenance
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- ports:
- - name: web
- port: 80
- targetPort: 80
- selector:
- ocelot.social/selector: deployment-maintenance
diff --git a/deployment/helm/ocelot.social/templates/services/service-neo4j.yaml b/deployment/helm/ocelot.social/templates/services/service-neo4j.yaml
deleted file mode 100644
index d9f1b14a0..000000000
--- a/deployment/helm/ocelot.social/templates/services/service-neo4j.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: {{ .Release.Name }}-neo4j
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- ports:
- - name: bolt
- port: 7687
- targetPort: 7687
- - name: web
- port: 7474
- targetPort: 7474
- selector:
- ocelot.social/selector: deployment-neo4j
diff --git a/deployment/helm/ocelot.social/templates/services/service-webapp.yaml b/deployment/helm/ocelot.social/templates/services/service-webapp.yaml
deleted file mode 100644
index c17e001ff..000000000
--- a/deployment/helm/ocelot.social/templates/services/service-webapp.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: {{ .Release.Name }}-webapp
- labels:
- app.kubernetes.io/instance: {{ .Release.Name }}
- app.kubernetes.io/managed-by: {{ .Release.Service }}
- app.kubernetes.io/name: ocelot-social
- app.kubernetes.io/version: {{ .Chart.AppVersion }}
- helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
-spec:
- ports:
- - name: {{ .Release.Name }}-webapp
- port: 3000
- protocol: TCP
- targetPort: 3000
- selector:
- ocelot.social/selector: deployment-webapp
diff --git a/deployment/helm/ocelot.social/templates/volumes/pvc-neo4j-data.yaml b/deployment/helm/ocelot.social/templates/volumes/pvc-neo4j-data.yaml
deleted file mode 100644
index 3f85d3ae8..000000000
--- a/deployment/helm/ocelot.social/templates/volumes/pvc-neo4j-data.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
- name: neo4j-data-claim
-spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: {{ .Values.neo4jStorage }}
diff --git a/deployment/helm/ocelot.social/templates/volumes/pvc-uploads.yaml b/deployment/helm/ocelot.social/templates/volumes/pvc-uploads.yaml
deleted file mode 100644
index 7eb81135b..000000000
--- a/deployment/helm/ocelot.social/templates/volumes/pvc-uploads.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-kind: PersistentVolumeClaim
-apiVersion: v1
-metadata:
- name: uploads-claim
-spec:
- dataSource:
- name: uploads-snapshot
- kind: VolumeSnapshot
- apiGroup: snapshot.storage.k8s.io
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: {{ .Values.uploadsStorage }}
-
diff --git a/deployment/helm/ocelot.social/values.yaml b/deployment/helm/ocelot.social/values.yaml
deleted file mode 100644
index 4c15c99a7..000000000
--- a/deployment/helm/ocelot.social/values.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-# domain is the user-facing domain.
-domain: develop-docker.ocelot.social
-# commit is the latest github commit deployed.
-commit: 889a7cdd24dda04a139b2b77d626e984d6db6781
-# dbInitialization runs the database initializations in a post-install hook.
-dbInitializion: "yarn prod:migrate init"
-# dbMigrations runs the database migrations in a post-upgrade hook.
-dbMigrations: "yarn prod:migrate up"
-# bakendImage is the docker image for the backend deployment
-backendImage: ocelotsocialnetwork/backend
-# maintenanceImage is the docker image for the maintenance deployment
-maintenanceImage: ocelotsocialnetwork/maintenance
-# neo4jImage is the docker image for the neo4j deployment
-neo4jImage: ocelotsocialnetwork/neo4j
-# webappImage is the docker image for the webapp deployment
-webappImage: ocelotsocialnetwork/webapp
-# image configures pullPolicy related to the docker images
-image:
- # pullPolicy indicates when, if ever, pods pull a new image from docker hub.
- pullPolicy: IfNotPresent
-# letsencryptIssuer is used by cert-manager to set up certificates with the given provider.
-letsencryptIssuer: "letsencrypt-prod"
-# neo4jConfig changes any default neo4j config/adds it.
-neo4jConfig:
- # acceptLicenseAgreement is used to agree to the license agreement for neo4j's enterprise edition.
- acceptLicenseAgreement: \"yes\"
- # apocImportFileEnabled enables the import of files to neo4j using the plugin apoc
- apocImportFileEnabled: \"true\"
- # dbmsMemoryHeapInitialSize configures initial heap size. By default, it is calculated based on available system resources.(valid units are `k`, `K`, `m`, `M`, `g`, `G`)
- dbmsMemoryHeapInitialSize: "500M"
- # dbmsMemoryHeapMaxSize configures maximum heap size. By default it is calculated based on available system resources.(valid units are `k`, `K`, `m`, `M`, `g`, `G`)
- dbmsMemoryHeapMaxSize: "500M"
- # dbmsMemoryPagecacheSize configures the amount of memory to use for mapping the store files, in bytes (or 'k', 'm', and 'g')
- dbmsMemoryPagecacheSize: "490M"
-# neo4jResourceLimitsMemory configures the memory limits available.
-neo4jResourceLimitsMemory: "2G"
-# neo4jResourceLimitsMemory configures the memory available for requests.
-neo4jResourceRequestsMemory: "1G"
-# supportEmail is used for letsencrypt certs.
-supportEmail: "devops@ocelot.social"
-# smtpHost is the host for the mailserver.
-smtpHost: "mail.ocelot.social"
-# smtpPort is the port to be used for the mailserver.
-smtpPort: \"25\"
-# jwtSecret is used to encode/decode a user's JWT for authentication
-jwtSecret: "Yi8mJjdiNzhCRiZmdi9WZA=="
-# privateKeyPassphrase is used for activity pub
-privateKeyPassphrase: "YTdkc2Y3OHNhZGc4N2FkODdzZmFnc2FkZzc4"
-# mapboxToken is used for the Mapbox API, geolocalization.
-mapboxToken: "cGsuZXlKMUlqb2lhSFZ0WVc0dFkyOXVibVZqZEdsdmJpSXNJbUVpT2lKamFqbDBjbkJ1Ykdvd2VUVmxNM1Z3WjJsek5UTnVkM1p0SW4wLktaOEtLOWw3MG9talhiRWtrYkhHc1E="
-uploadsStorage: "25Gi"
-neo4jStorage: "5Gi"
-developmentMailserverDomain: mail.ocelot.social
diff --git a/deployment/legacy-migration/README.md b/deployment/legacy-migration/README.md
deleted file mode 100644
index 66100a3c8..000000000
--- a/deployment/legacy-migration/README.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Legacy data migration
-
-This setup is **completely optional** and only required if you have data on a
-server which is running our legacy code and you want to import that data. It
-will import the uploads folder and migrate a dump of the legacy Mongo database
-into our new Neo4J graph database.
-
-## Configure Maintenance-Worker Pod
-
-Create a configmap with the specific connection data of your legacy server:
-
-```bash
-$ kubectl create configmap maintenance-worker \
- -n ocelot-social \
- --from-literal=SSH_USERNAME=someuser \
- --from-literal=SSH_HOST=yourhost \
- --from-literal=MONGODB_USERNAME=hc-api \
- --from-literal=MONGODB_PASSWORD=secretpassword \
- --from-literal=MONGODB_AUTH_DB=hc_api \
- --from-literal=MONGODB_DATABASE=hc_api \
- --from-literal=UPLOADS_DIRECTORY=/var/www/api/uploads
-```
-
-Create a secret with your public and private ssh keys. As the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys) points out, you should be careful with your ssh keys. Anyone with access to your cluster will have access to your ssh keys. Better create a new pair with `ssh-keygen` and copy the public key to your legacy server with `ssh-copy-id`:
-
-```bash
-$ kubectl create secret generic ssh-keys \
- -n ocelot-social \
- --from-file=id_rsa=/path/to/.ssh/id_rsa \
- --from-file=id_rsa.pub=/path/to/.ssh/id_rsa.pub \
- --from-file=known_hosts=/path/to/.ssh/known_hosts
-```
-
-## Deploy a Temporary Maintenance-Worker Pod
-
-Bring the application into maintenance mode.
-
-{% hint style="info" %} TODO: implement maintenance mode {% endhint %}
-
-
-Then temporarily delete backend and database deployments
-
-```bash
-$ kubectl -n ocelot-social get deployments
-NAME READY UP-TO-DATE AVAILABLE AGE
-backend 1/1 1 1 3d11h
-neo4j 1/1 1 1 3d11h
-webapp 2/2 2 2 73d
-$ kubectl -n ocelot-social delete deployment neo4j
-deployment.extensions "neo4j" deleted
-$ kubectl -n ocelot-social delete deployment backend
-deployment.extensions "backend" deleted
-```
-
-Deploy one-time develop-maintenance-worker pod:
-
-```bash
-# in deployment/legacy-migration/
-$ kubectl apply -f maintenance-worker.yaml
-pod/develop-maintenance-worker created
-```
-
-Import legacy database and uploads:
-
-```bash
-$ kubectl -n ocelot-social exec -it develop-maintenance-worker bash
-$ import_legacy_db
-$ import_legacy_uploads
-$ exit
-```
-
-Delete the pod when you're done:
-
-```bash
-$ kubectl -n ocelot-social delete pod develop-maintenance-worker
-```
-
-Oh, and of course you have to get those deleted deployments back. One way of
-doing it would be:
-
-```bash
-# in folder deployment/
-$ kubectl apply -f human-connection/deployment-backend.yaml -f human-connection/deployment-neo4j.yaml
-```
-
diff --git a/deployment/legacy-migration/maintenance-worker.yaml b/deployment/legacy-migration/maintenance-worker.yaml
deleted file mode 100644
index d8b118b67..000000000
--- a/deployment/legacy-migration/maintenance-worker.yaml
+++ /dev/null
@@ -1,40 +0,0 @@
----
- kind: Pod
- apiVersion: v1
- metadata:
- name: develop-maintenance-worker
- namespace: ocelot-social
- spec:
- containers:
- - name: develop-maintenance-worker
- image: ocelotsocialnetwork/develop-maintenance-worker:latest
- imagePullPolicy: Always
- resources:
- requests:
- memory: "2G"
- limits:
- memory: "8G"
- envFrom:
- - configMapRef:
- name: maintenance-worker
- - configMapRef:
- name: configmap
- volumeMounts:
- - name: secret-volume
- readOnly: false
- mountPath: /root/.ssh
- - name: uploads
- mountPath: /uploads
- - name: neo4j-data
- mountPath: /data/
- volumes:
- - name: secret-volume
- secret:
- secretName: ssh-keys
- defaultMode: 0400
- - name: uploads
- persistentVolumeClaim:
- claimName: uploads-claim
- - name: neo4j-data
- persistentVolumeClaim:
- claimName: neo4j-data-claim
diff --git a/deployment/legacy-migration/maintenance-worker/.dockerignore b/deployment/legacy-migration/maintenance-worker/.dockerignore
deleted file mode 100644
index 59ba63a8b..000000000
--- a/deployment/legacy-migration/maintenance-worker/.dockerignore
+++ /dev/null
@@ -1 +0,0 @@
-.ssh/
diff --git a/deployment/legacy-migration/maintenance-worker/.gitignore b/deployment/legacy-migration/maintenance-worker/.gitignore
deleted file mode 100644
index 485bc00e6..000000000
--- a/deployment/legacy-migration/maintenance-worker/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.ssh/
-ssh/
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/Dockerfile b/deployment/legacy-migration/maintenance-worker/Dockerfile
deleted file mode 100644
index 760cc06c8..000000000
--- a/deployment/legacy-migration/maintenance-worker/Dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-FROM ocelotsocialnetwork/develop-neo4j:latest
-
-ENV NODE_ENV=maintenance
-EXPOSE 7687 7474
-
-ENV BUILD_DEPS="gettext" \
- RUNTIME_DEPS="libintl"
-
-RUN set -x && \
- apk add --update $RUNTIME_DEPS && \
- apk add --virtual build_deps $BUILD_DEPS && \
- cp /usr/bin/envsubst /usr/local/bin/envsubst && \
- apk del build_deps
-
-
-RUN apk upgrade --update
-RUN apk add --no-cache mongodb-tools openssh nodejs yarn rsync
-
-COPY known_hosts /root/.ssh/known_hosts
-COPY migration /migration
-COPY ./binaries/* /usr/local/bin/
diff --git a/deployment/legacy-migration/maintenance-worker/binaries/.env b/deployment/legacy-migration/maintenance-worker/binaries/.env
deleted file mode 100644
index 773918095..000000000
--- a/deployment/legacy-migration/maintenance-worker/binaries/.env
+++ /dev/null
@@ -1,6 +0,0 @@
-# SSH Access
-# SSH_USERNAME='username'
-# SSH_HOST='example.org'
-
-# UPLOADS_DIRECTORY=/var/www/api/uploads
-OUTPUT_DIRECTORY='/uploads/'
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/binaries/idle b/deployment/legacy-migration/maintenance-worker/binaries/idle
deleted file mode 100755
index f5b1b2454..000000000
--- a/deployment/legacy-migration/maintenance-worker/binaries/idle
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/env bash
-tail -f /dev/null
diff --git a/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_db b/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_db
deleted file mode 100755
index 6ffdf8e3f..000000000
--- a/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_db
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-set -e
-for var in "SSH_USERNAME" "SSH_HOST" "MONGODB_USERNAME" "MONGODB_PASSWORD" "MONGODB_DATABASE" "MONGODB_AUTH_DB"
-do
- if [[ -z "${!var}" ]]; then
- echo "${var} is undefined"
- exit 1
- fi
-done
-
-/migration/mongo/export.sh
-/migration/neo4j/import.sh
diff --git a/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads b/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads
deleted file mode 100755
index 5c0b67d74..000000000
--- a/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-# import .env config
-set -o allexport
-source $(dirname "$0")/.env
-set +o allexport
-
-for var in "SSH_USERNAME" "SSH_HOST" "UPLOADS_DIRECTORY"
-do
- if [[ -z "${!var}" ]]; then
- echo "${var} is undefined"
- exit 1
- fi
-done
-
-rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/ ${OUTPUT_DIRECTORY}
diff --git a/deployment/legacy-migration/maintenance-worker/known_hosts b/deployment/legacy-migration/maintenance-worker/known_hosts
deleted file mode 100644
index 947840cb2..000000000
--- a/deployment/legacy-migration/maintenance-worker/known_hosts
+++ /dev/null
@@ -1,3 +0,0 @@
-|1|GuOYlVEhTowidPs18zj9p5F2j3o=|sDHJYLz9Ftv11oXeGEjs7SpVyg0= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM5N29bI5CeKu1/RBPyM2fwyf7fuajOO+tyhKe1+CC2sZ1XNB5Ff6t6MtCLNRv2mUuvzTbW/HkisDiA5tuXUHOk=
-|1|2KP9NV+Q5g2MrtjAeFSVcs8YeOI=|nf3h4wWVwC4xbBS1kzgzE2tBldk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E=
-|1|HonYIRNhKyroUHPKU1HSZw0+Qzs=|5T1btfwFBz2vNSldhqAIfTbfIgQ= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E=
diff --git a/deployment/legacy-migration/maintenance-worker/migration/mongo/.env b/deployment/legacy-migration/maintenance-worker/migration/mongo/.env
deleted file mode 100644
index 376cb56d0..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/mongo/.env
+++ /dev/null
@@ -1,17 +0,0 @@
-# SSH Access
-# SSH_USERNAME='username'
-# SSH_HOST='example.org'
-
-# Mongo DB on Remote Maschine
-# MONGODB_USERNAME='mongouser'
-# MONGODB_PASSWORD='mongopassword'
-# MONGODB_DATABASE='mongodatabase'
-# MONGODB_AUTH_DB='admin'
-
-# Export Settings
-# On Windows this resolves to C:\Users\dornhoeschen\AppData\Local\Temp\mongo-export (MinGW)
-EXPORT_PATH='/tmp/mongo-export/'
-EXPORT_MONGOEXPORT_BIN='mongoexport'
-MONGO_EXPORT_SPLIT_SIZE=6000
-# On Windows use something like this
-# EXPORT_MONGOEXPORT_BIN='C:\Program Files\MongoDB\Server\3.6\bin\mongoexport.exe'
diff --git a/deployment/legacy-migration/maintenance-worker/migration/mongo/export.sh b/deployment/legacy-migration/maintenance-worker/migration/mongo/export.sh
deleted file mode 100755
index b56ace87a..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/mongo/export.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-# import .env config
-set -o allexport
-source $(dirname "$0")/.env
-set +o allexport
-
-# Export collection function defintion
-function export_collection () {
- "${EXPORT_MONGOEXPORT_BIN}" --db ${MONGODB_DATABASE} --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --collection $1 --out "${EXPORT_PATH}$1.json"
- mkdir -p ${EXPORT_PATH}splits/$1/
- split -l ${MONGO_EXPORT_SPLIT_SIZE} -a 3 ${EXPORT_PATH}$1.json ${EXPORT_PATH}splits/$1/
-}
-
-# Export collection with query function defintion
-function export_collection_query () {
- "${EXPORT_MONGOEXPORT_BIN}" --db ${MONGODB_DATABASE} --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --collection $1 --out "${EXPORT_PATH}$1_$3.json" --query "$2"
- mkdir -p ${EXPORT_PATH}splits/$1_$3/
- split -l ${MONGO_EXPORT_SPLIT_SIZE} -a 3 ${EXPORT_PATH}$1_$3.json ${EXPORT_PATH}splits/$1_$3/
-}
-
-# Delete old export & ensure directory
-rm -rf ${EXPORT_PATH}*
-mkdir -p ${EXPORT_PATH}
-
-# Open SSH Tunnel
-ssh -4 -M -S my-ctrl-socket -fnNT -L 27018:localhost:27017 -l ${SSH_USERNAME} ${SSH_HOST}
-
-# Export all Data from the Alpha to json and split them up
-export_collection "badges"
-export_collection "categories"
-export_collection "comments"
-export_collection_query "contributions" '{"type": "DELETED"}' "DELETED"
-export_collection_query "contributions" '{"type": "post"}' "post"
-# export_collection_query "contributions" '{"type": "cando"}' "cando"
-export_collection "emotions"
-# export_collection_query "follows" '{"foreignService": "organizations"}' "organizations"
-export_collection_query "follows" '{"foreignService": "users"}' "users"
-# export_collection "invites"
-# export_collection "organizations"
-# export_collection "pages"
-# export_collection "projects"
-# export_collection "settings"
-export_collection "shouts"
-# export_collection "status"
-export_collection_query "users" '{"isVerified": true }' "verified"
-# export_collection "userscandos"
-# export_collection "usersettings"
-
-# Close SSH Tunnel
-ssh -S my-ctrl-socket -O check -l ${SSH_USERNAME} ${SSH_HOST}
-ssh -S my-ctrl-socket -O exit -l ${SSH_USERNAME} ${SSH_HOST}
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/.env b/deployment/legacy-migration/maintenance-worker/migration/neo4j/.env
deleted file mode 100644
index 16220f3e6..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/.env
+++ /dev/null
@@ -1,16 +0,0 @@
-# Neo4J Settings
-# NEO4J_USERNAME='neo4j'
-# NEO4J_PASSWORD='letmein'
-
-# Import Settings
-# On Windows this resolves to C:\Users\dornhoeschen\AppData\Local\Temp\mongo-export (MinGW)
-IMPORT_PATH='/tmp/mongo-export/'
-IMPORT_CHUNK_PATH='/tmp/mongo-export/splits/'
-
-IMPORT_CHUNK_PATH_CQL='/tmp/mongo-export/splits/'
-# On Windows this path needs to be windows style since the cypher-shell runs native - note the forward slash
-# IMPORT_CHUNK_PATH_CQL='C:/Users/dornhoeschen/AppData/Local/Temp/mongo-export/splits/'
-
-IMPORT_CYPHERSHELL_BIN='cypher-shell'
-# On Windows use something like this
-# IMPORT_CYPHERSHELL_BIN='C:\Program Files\neo4j-community\bin\cypher-shell.bat'
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/badges.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/badges.cql
deleted file mode 100644
index adf63dc1f..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/badges.cql
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[?] image: {
-[?] path: { // Path is incorrect in Nitro - is icon the correct name for this field?
-[X] type: String,
-[X] required: true
- },
-[ ] alt: { // If we use an image - should we not have an alt?
-[ ] type: String,
-[ ] required: true
- }
- },
-[?] status: {
-[X] type: String,
-[X] enum: ['permanent', 'temporary'],
-[ ] default: 'permanent', // Default value is missing in Nitro
-[X] required: true
- },
-[?] type: {
-[?] type: String, // in nitro this is a defined enum - seems good for now
-[X] required: true
- },
-[X] id: {
-[X] type: String,
-[X] required: true
- },
-[?] createdAt: {
-[?] type: Date, // Type is modeled as string in Nitro which is incorrect
-[ ] default: Date.now // Default value is missing in Nitro
- },
-[?] updatedAt: {
-[?] type: Date, // Type is modeled as string in Nitro which is incorrect
-[ ] default: Date.now // Default value is missing in Nitro
- }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as badge
-MERGE(b:Badge {id: badge._id["$oid"]})
-ON CREATE SET
-b.id = badge.key,
-b.type = badge.type,
-b.icon = replace(badge.image.path, 'https://api-alpha.human-connection.org', ''),
-b.status = badge.status,
-b.createdAt = badge.createdAt.`$date`,
-b.updatedAt = badge.updatedAt.`$date`
-;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/delete.cql
deleted file mode 100644
index 2a6f8c244..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/delete.cql
+++ /dev/null
@@ -1 +0,0 @@
-MATCH (n:Badge) DETACH DELETE n;
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/categories/categories.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/categories/categories.cql
deleted file mode 100644
index 5d4958876..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/categories/categories.cql
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[X] title: {
-[X] type: String,
-[X] required: true
- },
-[?] slug: {
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[ ] unique: true // Unique value is not enforced in Nitro?
- },
-[?] icon: { // Nitro adds required: true
-[X] type: String,
-[ ] unique: true // Unique value is not enforced in Nitro?
- },
-[?] createdAt: {
-[?] type: Date, // Type is modeled as string in Nitro which is incorrect
-[ ] default: Date.now // Default value is missing in Nitro
- },
-[?] updatedAt: {
-[?] type: Date, // Type is modeled as string in Nitro which is incorrect
-[ ] default: Date.now // Default value is missing in Nitro
- }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as category
-MERGE(c:Category {id: category._id["$oid"]})
-ON CREATE SET
-c.name = category.title,
-c.slug = category.slug,
-c.icon = category.icon,
-c.createdAt = category.createdAt.`$date`,
-c.updatedAt = category.updatedAt.`$date`
-;
-
-// Transform icon names
-MATCH (c:Category)
-WHERE (c.icon = "categories-justforfun")
-SET c.icon = 'smile'
-SET c.slug = 'just-for-fun'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-luck")
-SET c.icon = 'heart-o'
-SET c.slug = 'happiness-values'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-health")
-SET c.icon = 'medkit'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-environment")
-SET c.icon = 'tree'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-animal-justice")
-SET c.icon = 'paw'
-SET c.slug = 'animal-protection'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-human-rights")
-SET c.icon = 'balance-scale'
-SET c.slug = 'human-rights-justice'
-
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-education")
-SET c.icon = 'graduation-cap'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-cooperation")
-SET c.icon = 'users'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-politics")
-SET c.icon = 'university'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-economy")
-SET c.icon = 'money'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-technology")
-SET c.icon = 'flash'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-internet")
-SET c.icon = 'mouse-pointer'
-SET c.slug = 'it-internet-data-privacy'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-art")
-SET c.icon = 'paint-brush'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-freedom-of-speech")
-SET c.icon = 'bullhorn'
-SET c.slug = 'freedom-of-speech'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-sustainability")
-SET c.icon = 'shopping-cart'
-;
-
-MATCH (c:Category)
-WHERE (c.icon = "categories-peace")
-SET c.icon = 'angellist'
-SET c.slug = 'global-peace-nonviolence'
-;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/categories/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/categories/delete.cql
deleted file mode 100644
index c06b5ef2b..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/categories/delete.cql
+++ /dev/null
@@ -1 +0,0 @@
-MATCH (n:Category) DETACH DELETE n;
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/comments/comments.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/comments/comments.cql
deleted file mode 100644
index 083f9f762..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/comments/comments.cql
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[?] userId: {
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[-] index: true
- },
-[?] contributionId: {
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[-] index: true
- },
-[X] content: {
-[X] type: String,
-[X] required: true
- },
-[?] contentExcerpt: { // Generated from content
-[X] type: String,
-[ ] required: true // Not required in Nitro
- },
-[ ] hasMore: { type: Boolean },
-[ ] upvotes: {
-[ ] type: Array,
-[ ] default: []
- },
-[ ] upvoteCount: {
-[ ] type: Number,
-[ ] default: 0
- },
-[?] deleted: {
-[X] type: Boolean,
-[ ] default: false, // Default value is missing in Nitro
-[-] index: true
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as comment
-MERGE (c:Comment {id: comment._id["$oid"]})
-ON CREATE SET
-c.content = comment.content,
-c.contentExcerpt = comment.contentExcerpt,
-c.deleted = comment.deleted,
-c.createdAt = comment.createdAt.`$date`,
-c.updatedAt = comment.updatedAt.`$date`,
-c.disabled = false
-WITH c, comment, comment.contributionId as postId
-MATCH (post:Post {id: postId})
-WITH c, post, comment.userId as userId
-MATCH (author:User {id: userId})
-MERGE (c)-[:COMMENTS]->(post)
-MERGE (author)-[:WROTE]->(c)
-;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/comments/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/comments/delete.cql
deleted file mode 100644
index c4a7961c5..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/comments/delete.cql
+++ /dev/null
@@ -1 +0,0 @@
-MATCH (n:Comment) DETACH DELETE n;
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions/contributions.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions/contributions.cql
deleted file mode 100644
index f09b5ad71..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions/contributions.cql
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
-[?] { //Modeled incorrect as Post
-[?] userId: {
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[-] index: true
- },
-[ ] organizationId: {
-[ ] type: String,
-[-] index: true
- },
-[X] categoryIds: {
-[X] type: Array,
-[-] index: true
- },
-[X] title: {
-[X] type: String,
-[X] required: true
- },
-[?] slug: { // Generated from title
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[?] unique: true, // Unique value is not enforced in Nitro?
-[-] index: true
- },
-[ ] type: { // db.getCollection('contributions').distinct('type') -> 'DELETED', 'cando', 'post'
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] cando: {
-[ ] difficulty: {
-[ ] type: String,
-[ ] enum: ['easy', 'medium', 'hard']
- },
-[ ] reasonTitle: { type: String },
-[ ] reason: { type: String }
- },
-[X] content: {
-[X] type: String,
-[X] required: true
- },
-[?] contentExcerpt: { // Generated from content
-[X] type: String,
-[?] required: true // Not required in Nitro
- },
-[ ] hasMore: { type: Boolean },
-[X] teaserImg: { type: String },
-[ ] language: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] shoutCount: {
-[ ] type: Number,
-[ ] default: 0,
-[-] index: true
- },
-[ ] meta: {
-[ ] hasVideo: {
-[ ] type: Boolean,
-[ ] default: false
- },
-[ ] embedds: {
-[ ] type: Object,
-[ ] default: {}
- }
- },
-[?] visibility: {
-[X] type: String,
-[X] enum: ['public', 'friends', 'private'],
-[ ] default: 'public', // Default value is missing in Nitro
-[-] index: true
- },
-[?] isEnabled: {
-[X] type: Boolean,
-[ ] default: true, // Default value is missing in Nitro
-[-] index: true
- },
-[?] tags: { type: Array }, // ensure this is working properly
-[ ] emotions: {
-[ ] type: Object,
-[-] index: true,
-[ ] default: {
-[ ] angry: {
-[ ] count: 0,
-[ ] percent: 0
-[ ] },
-[ ] cry: {
-[ ] count: 0,
-[ ] percent: 0
-[ ] },
-[ ] surprised: {
-[ ] count: 0,
-[ ] percent: 0
- },
-[ ] happy: {
-[ ] count: 0,
-[ ] percent: 0
- },
-[ ] funny: {
-[ ] count: 0,
-[ ] percent: 0
- }
- }
- },
-[?] deleted: { // THis field is not always present in the alpha-data
-[?] type: Boolean,
-[ ] default: false, // Default value is missing in Nitro
-[-] index: true
- },
-[?] createdAt: {
-[?] type: Date, // Type is modeled as string in Nitro which is incorrect
-[ ] default: Date.now // Default value is missing in Nitro
- },
-[?] updatedAt: {
-[?] type: Date, // Type is modeled as string in Nitro which is incorrect
-[ ] default: Date.now // Default value is missing in Nitro
- },
-[ ] wasSeeded: { type: Boolean }
- }
-*/
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as post
-MERGE (p:Post {id: post._id["$oid"]})
-ON CREATE SET
-p.title = post.title,
-p.slug = post.slug,
-p.image = replace(post.teaserImg, 'https://api-alpha.human-connection.org', ''),
-p.content = post.content,
-p.contentExcerpt = post.contentExcerpt,
-p.visibility = toLower(post.visibility),
-p.createdAt = post.createdAt.`$date`,
-p.updatedAt = post.updatedAt.`$date`,
-p.deleted = COALESCE(post.deleted, false),
-p.disabled = COALESCE(NOT post.isEnabled, false)
-WITH p, post
-MATCH (u:User {id: post.userId})
-MERGE (u)-[:WROTE]->(p)
-WITH p, post, post.categoryIds as categoryIds
-UNWIND categoryIds AS categoryId
-MATCH (c:Category {id: categoryId})
-MERGE (p)-[:CATEGORIZED]->(c)
-WITH p, post.tags AS tags
-UNWIND tags AS tag
-WITH apoc.text.replace(tag, '[^\\p{L}0-9]', '') as tagNoSpacesAllowed
-CALL apoc.when(tagNoSpacesAllowed =~ '^((\\p{L}+[\\p{L}0-9]*)|([0-9]+\\p{L}+[\\p{L}0-9]*))$', 'RETURN tagNoSpacesAllowed', '', {tagNoSpacesAllowed: tagNoSpacesAllowed})
-YIELD value as validated
-WHERE validated.tagNoSpacesAllowed IS NOT NULL
-MERGE (t:Tag { id: validated.tagNoSpacesAllowed, disabled: false, deleted: false })
-MERGE (p)-[:TAGGED]->(t)
-;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions/delete.cql
deleted file mode 100644
index 70adad664..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions/delete.cql
+++ /dev/null
@@ -1,2 +0,0 @@
-MATCH (n:Post) DETACH DELETE n;
-MATCH (n:Tag) DETACH DELETE n;
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/delete_all.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/delete_all.cql
deleted file mode 100644
index d01871300..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/delete_all.cql
+++ /dev/null
@@ -1 +0,0 @@
-MATCH (n) DETACH DELETE n;
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/emotions/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/emotions/delete.cql
deleted file mode 100644
index 18fb6699f..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/emotions/delete.cql
+++ /dev/null
@@ -1 +0,0 @@
-MATCH (u:User)-[e:EMOTED]->(c:Post) DETACH DELETE e;
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/emotions/emotions.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/emotions/emotions.cql
deleted file mode 100644
index 06341f277..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/emotions/emotions.cql
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[X] userId: {
-[X] type: String,
-[X] required: true,
-[-] index: true
- },
-[X] contributionId: {
-[X] type: String,
-[X] required: true,
-[-] index: true
- },
-[?] rated: {
-[X] type: String,
-[ ] required: true,
-[?] enum: ['funny', 'happy', 'surprised', 'cry', 'angry']
- },
-[X] createdAt: {
-[X] type: Date,
-[X] default: Date.now
- },
-[X] updatedAt: {
-[X] type: Date,
-[X] default: Date.now
- },
-[-] wasSeeded: { type: Boolean }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as emotion
-MATCH (u:User {id: emotion.userId}),
- (c:Post {id: emotion.contributionId})
-MERGE (u)-[e:EMOTED {
- id: emotion._id["$oid"],
- emotion: emotion.rated,
- createdAt: datetime(emotion.createdAt.`$date`),
- updatedAt: datetime(emotion.updatedAt.`$date`)
- }]->(c)
-RETURN e;
-/*
- // Queries
- // user sets an emotion emotion:
- // MERGE (u)-[e:EMOTED {id: ..., emotion: "funny", createdAt: ..., updatedAt: ...}]->(c)
- // user removes emotion
- // MATCH (u)-[e:EMOTED]->(c) DELETE e
- // contribution distributions over every `emotion` property value for one post
- // MATCH (u:User)-[e:EMOTED]->(c:Post {id: "5a70bbc8508f5b000b443b1a"}) RETURN e.emotion,COUNT(e.emotion)
- // contribution distributions over every `emotion` property value for one user (advanced - "whats the primary emotion used by the user?")
- // MATCH (u:User{id:"5a663b1ac64291000bf302a1"})-[e:EMOTED]->(c:Post) RETURN e.emotion,COUNT(e.emotion)
- // contribution distributions over every `emotion` property value for all posts created by one user (advanced - "how do others react to my contributions?")
- // MATCH (u:User)-[e:EMOTED]->(c:Post)<-[w:WROTE]-(a:User{id:"5a663b1ac64291000bf302a1"}) RETURN e.emotion,COUNT(e.emotion)
- // if we can filter the above an a variable timescale that would be great (should be possible on createdAt and updatedAt fields)
-*/
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/follows/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/follows/delete.cql
deleted file mode 100644
index 3de01f8ea..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/follows/delete.cql
+++ /dev/null
@@ -1 +0,0 @@
-MATCH (u1:User)-[f:FOLLOWS]->(u2:User) DETACH DELETE f;
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/follows/follows.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/follows/follows.cql
deleted file mode 100644
index fac858a9a..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/follows/follows.cql
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[?] userId: {
-[-] type: String,
-[ ] required: true,
-[-] index: true
- },
-[?] foreignId: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[?] foreignService: { // db.getCollection('follows').distinct('foreignService') returns 'organizations' and 'users'
-[ ] type: String,
-[ ] required: true,
-[ ] index: true
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
- index:
-[?] { userId: 1, foreignId: 1, foreignService: 1 },{ unique: true } // is the unique constrain modeled?
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as follow
-MATCH (u1:User {id: follow.userId}), (u2:User {id: follow.foreignId})
-MERGE (u1)-[:FOLLOWS]->(u2)
-;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh b/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh
deleted file mode 100755
index ccb22dafb..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-# import .env config
-set -o allexport
-source $(dirname "$0")/.env
-set +o allexport
-
-# Delete collection function defintion
-function delete_collection () {
- # Delete from Database
- echo "Delete $2"
- "${IMPORT_CYPHERSHELL_BIN}" < $(dirname "$0")/$1/delete.cql > /dev/null
- # Delete index file
- rm -f "${IMPORT_PATH}splits/$2.index"
-}
-
-# Import collection function defintion
-function import_collection () {
- # index file of those chunks we have already imported
- INDEX_FILE="${IMPORT_PATH}splits/$1.index"
- # load index file
- if [ -f "$INDEX_FILE" ]; then
- readarray -t IMPORT_INDEX <$INDEX_FILE
- else
- declare -a IMPORT_INDEX
- fi
- # for each chunk import data
- for chunk in ${IMPORT_PATH}splits/$1/*
- do
- CHUNK_FILE_NAME=$(basename "${chunk}")
- # does the index not contain the chunk file name?
- if [[ ! " ${IMPORT_INDEX[@]} " =~ " ${CHUNK_FILE_NAME} " ]]; then
- # calculate the path of the chunk
- export IMPORT_CHUNK_PATH_CQL_FILE="${IMPORT_CHUNK_PATH_CQL}$1/${CHUNK_FILE_NAME}"
- # load the neo4j command and replace file variable with actual path
- NEO4J_COMMAND="$(envsubst '${IMPORT_CHUNK_PATH_CQL_FILE}' < $(dirname "$0")/$2)"
- # run the import of the chunk
- echo "Import $1 ${CHUNK_FILE_NAME} (${chunk})"
- echo "${NEO4J_COMMAND}" | "${IMPORT_CYPHERSHELL_BIN}" > /dev/null
- # add file to array and file
- IMPORT_INDEX+=("${CHUNK_FILE_NAME}")
- echo "${CHUNK_FILE_NAME}" >> ${INDEX_FILE}
- else
- echo "Skipping $1 ${CHUNK_FILE_NAME} (${chunk})"
- fi
- done
-}
-
-# Time variable
-SECONDS=0
-
-# Delete all Neo4J Database content
-echo "Deleting Database Contents"
-delete_collection "badges" "badges"
-delete_collection "categories" "categories"
-delete_collection "users" "users"
-delete_collection "follows" "follows_users"
-delete_collection "contributions" "contributions_post"
-delete_collection "contributions" "contributions_cando"
-delete_collection "shouts" "shouts"
-delete_collection "comments" "comments"
-delete_collection "emotions" "emotions"
-
-#delete_collection "invites"
-#delete_collection "notifications"
-#delete_collection "organizations"
-#delete_collection "pages"
-#delete_collection "projects"
-#delete_collection "settings"
-#delete_collection "status"
-#delete_collection "systemnotifications"
-#delete_collection "userscandos"
-#delete_collection "usersettings"
-echo "DONE"
-
-# Import Data
-echo "Start Importing Data"
-import_collection "badges" "badges/badges.cql"
-import_collection "categories" "categories/categories.cql"
-import_collection "users_verified" "users/users.cql"
-import_collection "follows_users" "follows/follows.cql"
-#import_collection "follows_organizations" "follows/follows.cql"
-import_collection "contributions_post" "contributions/contributions.cql"
-#import_collection "contributions_cando" "contributions/contributions.cql"
-#import_collection "contributions_DELETED" "contributions/contributions.cql"
-import_collection "shouts" "shouts/shouts.cql"
-import_collection "comments" "comments/comments.cql"
-import_collection "emotions" "emotions/emotions.cql"
-
-# import_collection "invites"
-# import_collection "notifications"
-# import_collection "organizations"
-# import_collection "pages"
-# import_collection "systemnotifications"
-# import_collection "userscandos"
-# import_collection "usersettings"
-
-# does only contain dummy data
-# import_collection "projects"
-
-# does only contain alpha specifc data
-# import_collection "status
-# import_collection "settings""
-
-echo "DONE"
-
-echo "Time elapsed: $SECONDS seconds"
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/invites/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/invites/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/invites/invites.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/invites/invites.cql
deleted file mode 100644
index f4a5bf006..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/invites/invites.cql
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] email: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true,
-[ ] unique: true
- },
-[ ] code: {
-[ ] type: String,
-[-] index: true,
-[ ] required: true
- },
-[ ] role: {
-[ ] type: String,
-[ ] enum: ['admin', 'moderator', 'manager', 'editor', 'user'],
-[ ] default: 'user'
- },
-[ ] invitedByUserId: { type: String },
-[ ] language: { type: String },
-[ ] badgeIds: [],
-[ ] wasUsed: {
-[ ] type: Boolean,
-[-] index: true
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as invite;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/notifications/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/notifications/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/notifications/notifications.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/notifications/notifications.cql
deleted file mode 100644
index aa6ac8eb9..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/notifications/notifications.cql
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] userId: { // User this notification is sent to
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] type: {
-[ ] type: String,
-[ ] required: true,
-[ ] enum: ['comment','comment-mention','contribution-mention','following-contribution']
- },
-[ ] relatedUserId: {
-[ ] type: String,
-[-] index: true
- },
-[ ] relatedContributionId: {
-[ ] type: String,
-[-] index: true
- },
-[ ] relatedOrganizationId: {
-[ ] type: String,
-[-] index: true
- },
-[ ] relatedCommentId: {type: String },
-[ ] unseen: {
-[ ] type: Boolean,
-[ ] default: true,
-[-] index: true
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as notification;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/organizations/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/organizations/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/organizations/organizations.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/organizations/organizations.cql
deleted file mode 100644
index e473e697c..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/organizations/organizations.cql
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] name: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] slug: {
-[ ] type: String,
-[ ] required: true,
-[ ] unique: true,
-[-] index: true
- },
-[ ] followersCounts: {
-[ ] users: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] organizations: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] projects: {
-[ ] type: Number,
-[ ] default: 0
- }
- },
-[ ] followingCounts: {
-[ ] users: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] organizations: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] projects: {
-[ ] type: Number,
-[ ] default: 0
- }
- },
-[ ] categoryIds: {
-[ ] type: Array,
-[ ] required: true,
-[-] index: true
- },
-[ ] logo: { type: String },
-[ ] coverImg: { type: String },
-[ ] userId: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] description: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] descriptionExcerpt: { type: String }, // will be generated automatically
-[ ] publicEmail: { type: String },
-[ ] url: { type: String },
-[ ] type: {
-[ ] type: String,
-[-] index: true,
-[ ] enum: ['ngo', 'npo', 'goodpurpose', 'ev', 'eva']
- },
-[ ] language: {
-[ ] type: String,
-[ ] required: true,
-[ ] default: 'de',
-[-] index: true
- },
-[ ] addresses: {
-[ ] type: [{
-[ ] street: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] zipCode: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] city: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] country: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] lat: {
-[ ] type: Number,
-[ ] required: true
- },
-[ ] lng: {
-[ ] type: Number,
-[ ] required: true
- }
- }],
-[ ] default: []
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] isEnabled: {
-[ ] type: Boolean,
-[ ] default: false,
-[-] index: true
- },
-[ ] reviewedBy: {
-[ ] type: String,
-[ ] default: null,
-[-] index: true
- },
-[ ] tags: {
-[ ] type: Array,
-[-] index: true
- },
-[ ] deleted: {
-[ ] type: Boolean,
-[ ] default: false,
-[-] index: true
- },
-[ ] wasSeeded: { type: Boolean }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as organisation;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/pages/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/pages/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/pages/pages.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/pages/pages.cql
deleted file mode 100644
index 18223136b..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/pages/pages.cql
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] title: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] slug: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] type: {
-[ ] type: String,
-[ ] required: true,
-[ ] default: 'page'
- },
-[ ] key: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] content: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] language: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] active: {
-[ ] type: Boolean,
-[ ] default: true,
-[-] index: true
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
- index:
-[ ] { slug: 1, language: 1 },{ unique: true }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as page;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/projects/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/projects/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/projects/projects.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/projects/projects.cql
deleted file mode 100644
index ed859c157..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/projects/projects.cql
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] name: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] slug: { type: String },
-[ ] followerIds: [],
-[ ] categoryIds: { type: Array },
-[ ] logo: { type: String },
-[ ] userId: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] description: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] content: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] addresses: {
-[ ] type: Array,
-[ ] default: []
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as project;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/settings/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/settings/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/settings/settings.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/settings/settings.cql
deleted file mode 100644
index 1d557d30c..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/settings/settings.cql
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] key: {
-[ ] type: String,
-[ ] default: 'system',
-[-] index: true,
-[ ] unique: true
- },
-[ ] invites: {
-[ ] userCanInvite: {
-[ ] type: Boolean,
-[ ] required: true,
-[ ] default: false
- },
-[ ] maxInvitesByUser: {
-[ ] type: Number,
-[ ] required: true,
-[ ] default: 1
- },
-[ ] onlyUserWithBadgesCanInvite: {
-[ ] type: Array,
-[ ] default: []
- }
- },
-[ ] maintenance: false
- }, {
-[ ] timestamps: true
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as setting;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts/delete.cql
deleted file mode 100644
index 21c2e1f90..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts/delete.cql
+++ /dev/null
@@ -1 +0,0 @@
-// this is just a relation between users and contributions - no need to delete
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts/shouts.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts/shouts.cql
deleted file mode 100644
index d370b4b4a..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts/shouts.cql
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[?] userId: {
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[-] index: true
- },
-[?] foreignId: {
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[-] index: true
- },
-[?] foreignService: { // db.getCollection('shots').distinct('foreignService') returns 'contributions'
-[X] type: String,
-[ ] required: true, // Not required in Nitro
-[-] index: true
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
- index:
-[?] { userId: 1, foreignId: 1 },{ unique: true } // is the unique constrain modeled?
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as shout
-MATCH (u:User {id: shout.userId}), (p:Post {id: shout.foreignId})
-MERGE (u)-[:SHOUTED]->(p)
-;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/status/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/status/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/status/status.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/status/status.cql
deleted file mode 100644
index 010c2ca09..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/status/status.cql
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] maintenance: {
-[ ] type: Boolean,
-[ ] default: false
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as status;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/systemnotifications/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/systemnotifications/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/systemnotifications/systemnotifications.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/systemnotifications/systemnotifications.cql
deleted file mode 100644
index 4bd33eb7c..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/systemnotifications/systemnotifications.cql
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] type: {
-[ ] type: String,
-[ ] default: 'info',
-[ ] required: true,
-[-] index: true
- },
-[ ] title: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] content: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] slot: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] language: {
-[ ] type: String,
-[ ] required: true,
-[-] index: true
- },
-[ ] permanent: {
-[ ] type: Boolean,
-[ ] default: false
- },
-[ ] requireConfirmation: {
-[ ] type: Boolean,
-[ ] default: false
- },
-[ ] active: {
-[ ] type: Boolean,
-[ ] default: true,
-[-] index: true
- },
-[ ] totalCount: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as systemnotification;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/users/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/users/delete.cql
deleted file mode 100644
index 32679f6c8..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/users/delete.cql
+++ /dev/null
@@ -1,2 +0,0 @@
-MATCH (n:User) DETACH DELETE n;
-MATCH (e:EmailAddress) DETACH DELETE e;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/users/users.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/users/users.cql
deleted file mode 100644
index 02dff089f..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/users/users.cql
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[?] email: {
-[X] type: String,
-[-] index: true,
-[X] required: true,
-[?] unique: true //unique constrain missing in Nitro
- },
-[?] password: { // Not required in Alpha -> verify if always present
-[X] type: String
- },
-[X] name: { type: String },
-[X] slug: {
-[X] type: String,
-[-] index: true
- },
-[ ] gender: { type: String },
-[ ] followersCounts: {
-[ ] users: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] organizations: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] projects: {
-[ ] type: Number,
-[ ] default: 0
- }
- },
-[ ] followingCounts: {
-[ ] users: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] organizations: {
-[ ] type: Number,
-[ ] default: 0
- },
-[ ] projects: {
-[ ] type: Number,
-[ ] default: 0
- }
- },
-[ ] timezone: { type: String },
-[X] avatar: { type: String },
-[X] coverImg: { type: String },
-[ ] doiToken: { type: String },
-[ ] confirmedAt: { type: Date },
-[?] badgeIds: [], // Verify this is working properly
-[?] deletedAt: { type: Date }, // The Date of deletion is not saved in Nitro
-[?] createdAt: {
-[?] type: Date, // Modeled as String in Nitro
-[ ] default: Date.now // Default value is missing in Nitro
- },
-[?] updatedAt: {
-[?] type: Date, // Modeled as String in Nitro
-[ ] default: Date.now // Default value is missing in Nitro
- },
-[ ] lastActiveAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] isVerified: { type: Boolean },
-[?] role: {
-[X] type: String,
-[-] index: true,
-[?] enum: ['admin', 'moderator', 'manager', 'editor', 'user'], // missing roles manager & editor in Nitro
-[ ] default: 'user' // Default value is missing in Nitro
- },
-[ ] verifyToken: { type: String },
-[ ] verifyShortToken: { type: String },
-[ ] verifyExpires: { type: Date },
-[ ] verifyChanges: { type: Object },
-[ ] resetToken: { type: String },
-[ ] resetShortToken: { type: String },
-[ ] resetExpires: { type: Date },
-[X] wasSeeded: { type: Boolean },
-[X] wasInvited: { type: Boolean },
-[ ] language: {
-[ ] type: String,
-[ ] default: 'en'
- },
-[ ] termsAndConditionsAccepted: { type: Date }, // we display the terms and conditions on registration
-[ ] systemNotificationsSeen: {
-[ ] type: Array,
-[ ] default: []
- }
- }
-*/
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as user
-MERGE(u:User {id: user._id["$oid"]})
-ON CREATE SET
-u.name = user.name,
-u.slug = COALESCE(user.slug, apoc.text.random(20, "[A-Za-z]")),
-u.email = user.email,
-u.encryptedPassword = user.password,
-u.avatar = replace(user.avatar, 'https://api-alpha.human-connection.org', ''),
-u.coverImg = replace(user.coverImg, 'https://api-alpha.human-connection.org', ''),
-u.wasInvited = user.wasInvited,
-u.wasSeeded = user.wasSeeded,
-u.role = toLower(user.role),
-u.createdAt = user.createdAt.`$date`,
-u.updatedAt = user.updatedAt.`$date`,
-u.deleted = user.deletedAt IS NOT NULL,
-u.disabled = false
-MERGE (e:EmailAddress {
- email: user.email,
- createdAt: toString(datetime()),
- verifiedAt: toString(datetime())
-})
-MERGE (e)-[:BELONGS_TO]->(u)
-MERGE (u)-[:PRIMARY_EMAIL]->(e)
-WITH u, user, user.badgeIds AS badgeIds
-UNWIND badgeIds AS badgeId
-MATCH (b:Badge {id: badgeId})
-MERGE (b)-[:REWARDED]->(u)
-;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/userscandos/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/userscandos/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/userscandos/userscandos.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/userscandos/userscandos.cql
deleted file mode 100644
index 55f58f171..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/userscandos/userscandos.cql
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] userId: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] contributionId: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] done: {
-[ ] type: Boolean,
-[ ] default: false
- },
-[ ] doneAt: { type: Date },
-[ ] createdAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- },
-[ ] wasSeeded: { type: Boolean }
- }
- index:
-[ ] { userId: 1, contributionId: 1 },{ unique: true }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as usercando;
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/usersettings/delete.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/usersettings/delete.cql
deleted file mode 100644
index e69de29bb..000000000
diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/usersettings/usersettings.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/usersettings/usersettings.cql
deleted file mode 100644
index 722625944..000000000
--- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/usersettings/usersettings.cql
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-// Alpha Model
-// [ ] Not modeled in Nitro
-// [X] Modeled in Nitro
-// [-] Omitted in Nitro
-// [?] Unclear / has work to be done for Nitro
- {
-[ ] userId: {
-[ ] type: String,
-[ ] required: true,
-[ ] unique: true
- },
-[ ] blacklist: {
-[ ] type: Array,
-[ ] default: []
- },
-[ ] uiLanguage: {
-[ ] type: String,
-[ ] required: true
- },
-[ ] contentLanguages: {
-[ ] type: Array,
-[ ] default: []
- },
-[ ] filter: {
-[ ] categoryIds: {
-[ ] type: Array,
-[ ] index: true
- },
-[ ] emotions: {
-[ ] type: Array,
-[ ] index: true
- }
- },
-[ ] hideUsersWithoutTermsOfUseSigniture: {type: Boolean},
-[ ] updatedAt: {
-[ ] type: Date,
-[ ] default: Date.now
- }
- }
-*/
-
-CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as usersetting;
diff --git a/deployment/minikube/README.md b/deployment/minikube/README.md
deleted file mode 100644
index 014f9510c..000000000
--- a/deployment/minikube/README.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Minikube
-
-There are many Kubernetes providers, but if you're just getting started, Minikube is a tool that you can use to get your feet wet.
-
-After you [installed Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/)
-open your minikube dashboard:
-
-```text
-$ minikube dashboard
-```
-
-This will give you an overview. Some of the steps below need some timing to make resources available to other dependent deployments. Keeping an eye on the dashboard is a great way to check that.
-
-Follow the installation instruction for [Human Connection](../ocelot-social/README.md).
-If all the pods and services have settled and everything looks green in your
-minikube dashboard, expose the services you want on your host system.
-
-For example:
-
-```text
-$ minikube service webapp --namespace=ocelotsocialnetwork
-# optionally
-$ minikube service backend --namespace=ocelotsocialnetwork
-```
-
diff --git a/deployment/monitoring/README.md b/deployment/monitoring/README.md
deleted file mode 100644
index 46dfb0301..000000000
--- a/deployment/monitoring/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Metrics
-
-You can optionally setup [prometheus](https://prometheus.io/) and
-[grafana](https://grafana.com/) for metrics.
-
-We follow this tutorial [here](https://medium.com/@chris_linguine/how-to-monitor-your-kubernetes-cluster-with-prometheus-and-grafana-2d5704187fc8):
-
-```bash
-kubectl proxy # proxy to your kubernetes dashboard
-
-helm repo list
-# If using helm v3, the stable repository is not set, so you need to manually add it.
-helm repo add stable https://kubernetes-charts.storage.googleapis.com
-# Create a monitoring namespace for your cluster
-kubectl create namespace monitoring
-helm --namespace monitoring install prometheus stable/prometheus
-kubectl -n monitoring get pods # look for 'server'
-kubectl port-forward -n monitoring 9090
-# You can now see your prometheus server on: http://localhost:9090
-
-# Make sure you are in folder `deployment/`
-kubectl apply -f monitoring/grafana/config.yml
-helm --namespace monitoring install grafana stable/grafana -f monitoring/grafana/values.yml
-# Get the admin password for grafana from your kubernetes dashboard.
-kubectl --namespace monitoring port-forward 3000
-# You can now see your grafana dashboard on: http://localhost:3000
-# Login with user 'admin' and the password you just looked up.
-# In your dashboard import this dashboard:
-# https://grafana.com/grafana/dashboards/1860
-# Enter ID 180 and choose "Prometheus" as datasource.
-# You got metrics!
-```
-
-Now you should see something like this:
-
-
-
-You can set up a grafana dashboard, by visiting https://grafana.com/dashboards, finding one that is suitable and copying it's id.
-You then go to the left hand menu in localhost, choose `Dashboard` > `Manage` > `Import`
-Paste in the id, click `Load`, select `Prometheus` for the data source, and click `Import`
-
-When you just installed prometheus and grafana, the data will not be available
-immediately, so wait for a couple of minutes and reload.
diff --git a/deployment/monitoring/grafana/config.yml b/deployment/monitoring/grafana/config.yml
deleted file mode 100644
index a338e3480..000000000
--- a/deployment/monitoring/grafana/config.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: prometheus-grafana-datasource
- namespace: monitoring
- labels:
- grafana_datasource: '1'
-data:
- datasource.yaml: |-
- apiVersion: 1
- datasources:
- - name: Prometheus
- type: prometheus
- access: proxy
- orgId: 1
- url: http://prometheus-server.monitoring.svc.cluster.local
diff --git a/deployment/monitoring/grafana/metrics.png b/deployment/monitoring/grafana/metrics.png
deleted file mode 100644
index cc68f1bad..000000000
Binary files a/deployment/monitoring/grafana/metrics.png and /dev/null differ
diff --git a/deployment/monitoring/grafana/values.yml b/deployment/monitoring/grafana/values.yml
deleted file mode 100644
index 02004cc1c..000000000
--- a/deployment/monitoring/grafana/values.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-sidecar:
- datasources:
- enabled: true
- label: grafana_datasource
diff --git a/deployment/namespace.yaml b/deployment/namespace.yaml
deleted file mode 100644
index 43898c546..000000000
--- a/deployment/namespace.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-kind: Namespace
-apiVersion: v1
-metadata:
- name: ocelot-social
- labels:
- name: ocelot-social
diff --git a/deployment/ocelot-social/README.md b/deployment/ocelot-social/README.md
deleted file mode 100644
index 29680f0c8..000000000
--- a/deployment/ocelot-social/README.md
+++ /dev/null
@@ -1,71 +0,0 @@
-# Kubernetes Configuration For ocelot.social
-
-Deploying *ocelot.social* with kubernetes is straight forward. All you have to
-do is to change certain parameters, like domain names and API keys, then you
-just apply our provided configuration files to your cluster.
-
-## Configuration
-
-Change into the `./deployment` directory and copy our provided templates:
-
-```bash
-# in folder deployment/ocelot-social/
-$ cp templates/secrets.template.yaml ./secrets.yaml
-$ cp templates/configmap.template.yaml ./configmap.yaml
-```
-
-Change the `configmap.yaml` in the `./deployment/ocelot-social` directory as needed, all variables will be available as
-environment variables in your deployed Kubernetes pods.
-
-Probably you want to change this environment variable to your actual domain:
-
-```yaml
-# in configmap.yaml
-CLIENT_URI: "https://develop-k8s.ocelot.social"
-```
-
-If you want to edit secrets, you have to `base64` encode them. See [Kubernetes Documentation](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret-manually).
-
-```bash
-# example how to base64 a string:
-$ echo -n 'admin' | base64
-YWRtaW4=
-```
-
-Those secrets get `base64` decoded and are available as environment variables in
-your deployed Kubernetes pods.
-
-## Create A Namespace
-
-```bash
-# in folder deployment/
-$ kubectl apply -f namespace.yaml
-```
-
-If you have a [Kubernets Dashboard](../digital-ocean/dashboard/README.md)
-deployed you should switch to namespace `ocelot-social` in order to
-monitor the state of your deployments.
-
-## Create Persistent Volumes
-
-While the deployments and services can easily be restored, simply by deleting
-and applying the Kubernetes configurations again, certain data is not that
-easily recovered. Therefore we separated persistent volumes from deployments
-and services. There is a [dedicated section](../volumes/README.md). Create those
-persistent volumes once before you apply the configuration.
-
-## Apply The Configuration
-
-Before you apply you should think about the size of the droplet(s) you need.
-For example, the requirements for Neo4j v3.5.14 are [here](https://neo4j.com/docs/operations-manual/3.5/installation/requirements/).
-Tips to configure the pod resources you find [here](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).
-
-```bash
-# in folder deployment/
-$ kubectl apply -f ocelot-social/
-```
-
-This can take a while, because Kubernetes will download the Docker images from Docker Hub. Sit
-back and relax and have a look into your kubernetes dashboard. Wait until all
-pods turn green and they don't show a warning `Waiting: ContainerCreating`
-anymore.
diff --git a/deployment/ocelot-social/deployment-backend.yaml b/deployment/ocelot-social/deployment-backend.yaml
deleted file mode 100644
index 1664686d9..000000000
--- a/deployment/ocelot-social/deployment-backend.yaml
+++ /dev/null
@@ -1,62 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- creationTimestamp: null
- labels:
- ocelot.social/commit: COMMIT
- ocelot.social/selector: deployment-ocelot-social-backend
- name: backend
- namespace: ocelot-social
-spec:
- minReadySeconds: 15
- progressDeadlineSeconds: 60
- replicas: 1
- revisionHistoryLimit: 2147483647
- selector:
- matchLabels:
- ocelot.social/selector: deployment-ocelot-social-backend
- strategy:
- rollingUpdate:
- maxSurge: 0
- maxUnavailable: 100%
- type: RollingUpdate
- template:
- metadata:
- annotations:
- backup.velero.io/backup-volumes: uploads
- creationTimestamp: null
- labels:
- ocelot.social/commit: COMMIT
- ocelot.social/selector: deployment-ocelot-social-backend
- name: backend
- spec:
- containers:
- - envFrom:
- - configMapRef:
- name: configmap
- - secretRef:
- name: ocelot-social
- image: ocelotsocialnetwork/develop-backend:latest # for develop
- # image: ocelotsocialnetwork/develop-backend:0.6.3 # for production or staging
- imagePullPolicy: Always # for develop or staging
- # imagePullPolicy: IfNotPresent # for production
- name: backend
- ports:
- - containerPort: 4000
- protocol: TCP
- resources: {}
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- volumeMounts:
- - mountPath: /develop-backend/public/uploads
- name: uploads
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- terminationGracePeriodSeconds: 30
- volumes:
- - name: uploads
- persistentVolumeClaim:
- claimName: uploads-claim
-status: {}
diff --git a/deployment/ocelot-social/deployment-neo4j.yaml b/deployment/ocelot-social/deployment-neo4j.yaml
deleted file mode 100644
index aa2c367c6..000000000
--- a/deployment/ocelot-social/deployment-neo4j.yaml
+++ /dev/null
@@ -1,65 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- creationTimestamp: null
- labels:
- ocelot.social/selector: deployment-ocelot-social-neo4j
- name: neo4j
- namespace: ocelot-social
-spec:
- progressDeadlineSeconds: 2147483647
- replicas: 1
- revisionHistoryLimit: 2147483647
- selector:
- matchLabels:
- ocelot.social/selector: deployment-ocelot-social-neo4j
- strategy:
- rollingUpdate:
- maxSurge: 0
- maxUnavailable: 100%
- type: RollingUpdate
- template:
- metadata:
- annotations:
- backup.velero.io/backup-volumes: neo4j-data
- creationTimestamp: null
- labels:
- ocelot.social/selector: deployment-ocelot-social-neo4j
- name: neo4j
- spec:
- containers:
- - envFrom:
- - configMapRef:
- name: configmap
- image: ocelotsocialnetwork/develop-neo4j:latest # for develop
- # image: ocelotsocialnetwork/develop-neo4j:0.6.3 # for production or staging
- imagePullPolicy: Always # for develop or staging
- # imagePullPolicy: IfNotPresent # for production
- name: neo4j
- ports:
- - containerPort: 7687
- protocol: TCP
- - containerPort: 7474
- protocol: TCP
- resources:
- # see description and add cpu https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
- # see requirements for Neo4j v3.5.14 https://neo4j.com/docs/operations-manual/3.5/installation/requirements/
- limits:
- memory: 2G
- requests:
- memory: 2G
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- volumeMounts:
- - mountPath: /data/
- name: neo4j-data
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- terminationGracePeriodSeconds: 30
- volumes:
- - name: neo4j-data
- persistentVolumeClaim:
- claimName: neo4j-data-claim
-status: {}
diff --git a/deployment/ocelot-social/deployment-webapp.yaml b/deployment/ocelot-social/deployment-webapp.yaml
deleted file mode 100644
index 2cc742deb..000000000
--- a/deployment/ocelot-social/deployment-webapp.yaml
+++ /dev/null
@@ -1,54 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- creationTimestamp: null
- labels:
- ocelot.social/commit: COMMIT
- ocelot.social/selector: deployment-ocelot-social-webapp
- name: web
- namespace: ocelot-social
-spec:
- minReadySeconds: 15
- progressDeadlineSeconds: 60
- replicas: 2
- revisionHistoryLimit: 2147483647
- selector:
- matchLabels:
- ocelot.social/selector: deployment-ocelot-social-webapp
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: RollingUpdate
- template:
- metadata:
- creationTimestamp: null
- labels:
- ocelot.social/commit: COMMIT
- ocelot.social/selector: deployment-ocelot-social-webapp
- name: web
- spec:
- containers:
- - env:
- - name: HOST
- value: 0.0.0.0
- envFrom:
- - configMapRef:
- name: configmap
- - secretRef:
- name: ocelot-social
- image: ocelotsocialnetwork/webapp:latest
- imagePullPolicy: Always
- name: web
- ports:
- - containerPort: 3000
- protocol: TCP
- resources: {}
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- terminationGracePeriodSeconds: 30
-status: {}
diff --git a/deployment/ocelot-social/error-reporting/README.md b/deployment/ocelot-social/error-reporting/README.md
deleted file mode 100644
index 5a4eac7c9..000000000
--- a/deployment/ocelot-social/error-reporting/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Error reporting
-
-We use [Sentry](https://github.com/getsentry/sentry) for error reporting in both
-our backend and web frontend. You can either use a hosted or a self-hosted
-instance. Just set the two `DSN` in your
-[configmap](../templates/configmap.template.yaml) and update the `COMMIT`
-during a deployment with your commit or the version of your release.
-
-## Self-hosted Sentry
-
-For data privacy it is recommended to set up your own instance of sentry.
-If you are lucky enough to have a kubernetes cluster with the required hardware
-support, try this [helm chart](https://github.com/helm/charts/tree/master/stable/sentry).
-
-On our kubernetes cluster we get "mult-attach" errors for persistent volumes.
-Apparently Digital Ocean's kubernetes clusters do not fulfill the requirements.
diff --git a/deployment/ocelot-social/mailserver/README.md b/deployment/ocelot-social/mailserver/README.md
deleted file mode 100644
index ed9292d5c..000000000
--- a/deployment/ocelot-social/mailserver/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Development Mail Server
-
-You can deploy a fake smtp server which captures all send mails and displays
-them in a web interface. The [sample configuration](../templates/configmap.template.yaml)
-is assuming such a dummy server in the `SMTP_HOST` configuration and points to
-a cluster-internal SMTP server.
-
-To deploy the SMTP server just uncomment the relevant code in the
-[ingress server configuration](../../https/templates/ingress.template.yaml) and
-run the following:
-
-```bash
-# in folder deployment/ocelot-social
-$ kubectl apply -f mailserver/
-```
-
-You might need to refresh the TLS secret to enable HTTPS on the publicly
-available web interface.
diff --git a/deployment/ocelot-social/mailserver/deployment-mailserver.yaml b/deployment/ocelot-social/mailserver/deployment-mailserver.yaml
deleted file mode 100644
index dc5c49347..000000000
--- a/deployment/ocelot-social/mailserver/deployment-mailserver.yaml
+++ /dev/null
@@ -1,51 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- creationTimestamp: null
- labels:
- ocelot.social/selector: deployment-ocelot-social-mailserver
- name: mailserver
- namespace: ocelot-social
-spec:
- minReadySeconds: 15
- progressDeadlineSeconds: 60
- replicas: 1
- revisionHistoryLimit: 2147483647
- selector:
- matchLabels:
- ocelot.social/selector: deployment-ocelot-social-mailserver
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: RollingUpdate
- template:
- metadata:
- creationTimestamp: null
- labels:
- ocelot.social/selector: deployment-ocelot-social-mailserver
- name: mailserver
- spec:
- containers:
- - envFrom:
- - configMapRef:
- name: configmap
- - secretRef:
- name: ocelot-social
- image: djfarrelly/maildev
- imagePullPolicy: Always
- name: mailserver
- ports:
- - containerPort: 80
- protocol: TCP
- - containerPort: 25
- protocol: TCP
- resources: {}
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- terminationGracePeriodSeconds: 30
-status: {}
diff --git a/deployment/ocelot-social/mailserver/service-mailserver.yaml b/deployment/ocelot-social/mailserver/service-mailserver.yaml
deleted file mode 100644
index 8c38a94b4..000000000
--- a/deployment/ocelot-social/mailserver/service-mailserver.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: mailserver
- namespace: ocelot-social
- labels:
- ocelot.social/selector: deployment-ocelot-social-mailserver
-spec:
- ports:
- - name: web
- port: 80
- targetPort: 80
- - name: smtp
- port: 25
- targetPort: 25
- selector:
- ocelot.social/selector: deployment-ocelot-social-mailserver
diff --git a/deployment/ocelot-social/maintenance/README.md b/deployment/ocelot-social/maintenance/README.md
deleted file mode 100644
index 08a177e65..000000000
--- a/deployment/ocelot-social/maintenance/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Maintenance mode
-
-> Despite our best efforts, systems sometimes require downtime for a variety of reasons.
-
-Quote from [here](https://www.nrmitchi.com/2017/11/easy-maintenance-mode-in-kubernetes/)
-
-We use our maintenance mode for manual database backup and restore. Also we
-bring the database into maintenance mode for manual database migrations.
-
-## Deploy the service
-
-We prepared sample configuration, so you can simply run:
-
-```sh
-# in folder deployment/
-$ kubectl apply -f ./ocelot-social/maintenance/
-```
-
-This will fire up a maintenance service.
-
-## Bring application into maintenance mode
-
-Now if you want to have a controlled downtime and you want to bring your
-application into maintenance mode, you can edit your global ingress server.
-
-E.g. copy file [`deployment/digital-ocean/https/templates/ingress.template.yaml`](../../digital-ocean/https/templates/ingress.template.yaml) to new file `deployment/digital-ocean/https/ingress.yaml` and change the following:
-
-```yaml
-...
-
- - host: develop-k8s.ocelot.social
- http:
- paths:
- - path: /
- backend:
- # serviceName: web
- serviceName: maintenance
- # servicePort: 3000
- servicePort: 80
-```
-
-Then run `$ kubectl apply -f deployment/digital-ocean/https/ingress.yaml`. If you
-want to deactivate the maintenance server, just undo the edit and apply the
-configuration again.
-
diff --git a/deployment/ocelot-social/maintenance/deployment-maintenance.yaml b/deployment/ocelot-social/maintenance/deployment-maintenance.yaml
deleted file mode 100644
index 8c38aab66..000000000
--- a/deployment/ocelot-social/maintenance/deployment-maintenance.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: maintenance
- namespace: ocelot-social
-spec:
- selector:
- matchLabels:
- ocelot.social/selector: deployment-ocelot-social-maintenance
- template:
- metadata:
- labels:
- ocelot.social/commit: COMMIT
- ocelot.social/selector: deployment-ocelot-social-maintenance
- name: maintenance
- spec:
- containers:
- - name: web
- env:
- - name: HOST
- value: 0.0.0.0
- image: ocelotsocialnetwork/develop-maintenance:latest
- ports:
- - containerPort: 80
- imagePullPolicy: Always
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
diff --git a/deployment/ocelot-social/maintenance/service-maintenance.yaml b/deployment/ocelot-social/maintenance/service-maintenance.yaml
deleted file mode 100644
index 7c636e253..000000000
--- a/deployment/ocelot-social/maintenance/service-maintenance.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: maintenance
- namespace: ocelot-social
- labels:
- ocelot.social/selector: deployment-ocelot-social-maintenance
-spec:
- ports:
- - name: web
- port: 80
- targetPort: 80
- selector:
- ocelot.social/selector: deployment-ocelot-social-maintenance
diff --git a/deployment/ocelot-social/service-backend.yaml b/deployment/ocelot-social/service-backend.yaml
deleted file mode 100644
index 16d5859f8..000000000
--- a/deployment/ocelot-social/service-backend.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: backend
- namespace: ocelot-social
- labels:
- ocelot.social/selector: deployment-ocelot-social-backend
-spec:
- ports:
- - name: web
- port: 4000
- targetPort: 4000
- selector:
- ocelot.social/selector: deployment-ocelot-social-backend
diff --git a/deployment/ocelot-social/service-neo4j.yaml b/deployment/ocelot-social/service-neo4j.yaml
deleted file mode 100644
index 2a0e404ea..000000000
--- a/deployment/ocelot-social/service-neo4j.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: neo4j
- namespace: ocelot-social
- labels:
- ocelot.social/selector: deployment-ocelot-social-neo4j
-spec:
- ports:
- - name: bolt
- port: 7687
- targetPort: 7687
- - name: web
- port: 7474
- targetPort: 7474
- selector:
- ocelot.social/selector: deployment-ocelot-social-neo4j
diff --git a/deployment/ocelot-social/service-webapp.yaml b/deployment/ocelot-social/service-webapp.yaml
deleted file mode 100644
index a3acacb11..000000000
--- a/deployment/ocelot-social/service-webapp.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- name: web
- namespace: ocelot-social
- labels:
- ocelot.social/selector: deployment-ocelot-social-webapp
-spec:
- ports:
- - name: web
- port: 3000
- targetPort: 3000
- selector:
- ocelot.social/selector: deployment-ocelot-social-webapp
diff --git a/deployment/ocelot-social/templates/configmap.template.yaml b/deployment/ocelot-social/templates/configmap.template.yaml
deleted file mode 100644
index daa5c7921..000000000
--- a/deployment/ocelot-social/templates/configmap.template.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-data:
- # decomment following lines for S3 bucket to store our images
- # AWS_ACCESS_KEY_ID: see secrets
- # AWS_BUCKET: ocelot-social-uploads
- # AWS_ENDPOINT: fra1.digitaloceanspaces.com
- # AWS_REGION: fra1
- # AWS_SECRET_ACCESS_KEY: see secrets
- CLIENT_URI: "https://develop-k8s.ocelot.social" # change this to your domain
- COMMIT: ""
- EMAIL_DEFAULT_SENDER: devops@ocelot.social # change this to your e-mail
- GRAPHQL_PORT: "4000"
- GRAPHQL_URI: "http://backend.ocelot-social:4000" # leave this as ocelot-social
- # decomment following line for Neo4j Enterprice version instead of Community version
- # NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes"
- NEO4J_AUTH: "none"
- # NEO4J_dbms_connector_bolt_thread__pool__max__size: "10000"
- NEO4J_apoc_import_file_enabled: "true"
- NEO4J_dbms_memory_heap_initial__size: "500M"
- NEO4J_dbms_memory_heap_max__size: "500M"
- NEO4J_dbms_memory_pagecache_size: "490M"
- NEO4J_dbms_security_procedures_unrestricted: "algo.*,apoc.*"
- NEO4J_URI: "bolt://neo4j.ocelot-social:7687" # leave this as ocelot-social
- PUBLIC_REGISTRATION: "false"
- REDIS_DOMAIN: ---toBeSet(IP)---
- # REDIS_PASSWORD: see secrets
- REDIS_PORT: "6379"
- SENTRY_DSN_WEBAPP: "---toBeSet---"
- SENTRY_DSN_BACKEND: "---toBeSet---"
- SMTP_HOST: "mail.ocelot.social" # change this to your domain
- # SMTP_PASSWORD: see secrets
- SMTP_PORT: "25" # change this to your port
- # SMTP_USERNAME: see secrets
- SMTP_IGNORE_TLS: 'true' # change this to your setting
- WEBSOCKETS_URI: wss://develop-k8s.ocelot.social/api/graphql # change this to your domain
-metadata:
- name: configmap
- namespace: ocelot-social
diff --git a/deployment/ocelot-social/templates/secrets.template.yaml b/deployment/ocelot-social/templates/secrets.template.yaml
deleted file mode 100644
index 1e4865c8d..000000000
--- a/deployment/ocelot-social/templates/secrets.template.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Secret
-data:
- # decomment following lines for S3 bucket to store our images
- # AWS_ACCESS_KEY_ID: ---toBeSet---
- # AWS_SECRET_ACCESS_KEY: ---toBeSet---
- JWT_SECRET: "Yi8mJjdiNzhCRiZmdi9WZA=="
- PRIVATE_KEY_PASSPHRASE: "YTdkc2Y3OHNhZGc4N2FkODdzZmFnc2FkZzc4"
- MAPBOX_TOKEN: "---toBeSet(IP)---"
- NEO4J_USERNAME: ""
- NEO4J_PASSWORD: ""
- REDIS_PASSWORD: ---toBeSet---
- SMTP_PASSWORD: "---toBeSet---"
- SMTP_USERNAME: "---toBeSet---"
-metadata:
- name: ocelot-social
- namespace: ocelot-social
diff --git a/deployment/volumes/README.md b/deployment/volumes/README.md
deleted file mode 100644
index 1d849682c..000000000
--- a/deployment/volumes/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Persistent Volumes
-
-At the moment, the application needs two persistent volumes:
-
-* The `/data/` folder where `neo4j` stores its database and
-* the folder `/develop-backend/public/uploads` where the backend stores uploads, in case you don't use Digital Ocean Spaces (an AWS S3 bucket) for this purpose.
-
-As a matter of precaution, the persistent volume claims that setup these volumes
-live in a separate folder. You don't want to accidently loose all your data in
-your database by running
-
-```sh
-kubectl delete -f ocelot-social/
-```
-
-or do you?
-
-## Create Persistent Volume Claims
-
-Run the following:
-
-```sh
-# in folder deployments/
-$ kubectl apply -f volumes
-persistentvolumeclaim/neo4j-data-claim created
-persistentvolumeclaim/uploads-claim created
-```
-
-## Backup And Restore
-
-We tested a couple of options how to do disaster recovery in kubernetes. First,
-there is the [offline backup strategy](./neo4j-offline-backup/README.md) of the
-community edition of Neo4J, which you can also run on a local installation.
-Kubernetes also offers so-called [volume snapshots](./volume-snapshots/README.md).
-Changing the [reclaim policy](./reclaim-policy/README.md) of your persistent
-volumes might be an additional safety measure. Finally, there is also a
-kubernetes specific disaster recovery tool called [Velero](./velero/README.md).
diff --git a/deployment/volumes/neo4j-data.yaml b/deployment/volumes/neo4j-data.yaml
deleted file mode 100644
index 1053d0105..000000000
--- a/deployment/volumes/neo4j-data.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
----
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: neo4j-data-claim
- namespace: ocelot-social
- spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: "10Gi" # see requirements for Neo4j v3.5.14 https://neo4j.com/docs/operations-manual/3.5/installation/requirements/
diff --git a/deployment/volumes/neo4j-offline-backup/README.md b/deployment/volumes/neo4j-offline-backup/README.md
deleted file mode 100644
index 7c34aa764..000000000
--- a/deployment/volumes/neo4j-offline-backup/README.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# Backup (offline)
-
-This tutorial explains how to carry out an offline backup of your Neo4J
-database in a kubernetes cluster.
-
-An offline backup requires the Neo4J database to be stopped. Read
-[the docs](https://neo4j.com/docs/operations-manual/current/tools/dump-load/).
-Neo4J also offers online backups but this is available in enterprise edition
-only.
-
-The tricky part is to stop the Neo4J database *without* stopping the container.
-Neo4J's docker container image starts `neo4j` by default, so we have to override
-this command with sth. that keeps the container spinning but does not terminate
-it.
-
-## Stop and Restart Neo4J Database in Kubernetes
-
-[This tutorial](http://bigdatums.net/2017/11/07/how-to-keep-docker-containers-running/)
-explains how to keep a docker container running. For kubernetes, the way to
-override the docker image `CMD` is explained [here](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#define-a-command-and-arguments-when-you-create-a-pod).
-
-So, all we have to do is edit the kubernetes deployment of our Neo4J database
-and set a custom `command` every time we have to carry out tasks like backup,
-restore, seed etc.
-
-First bring the application into [maintenance mode](https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/deployment/ocelot-social/maintenance/README.md) to ensure there are no
-database connections left and nobody can access the application.
-
-Run the following:
-
-```sh
-$ kubectl -n ocelot-social edit deployment develop-neo4j
-```
-
-Add the following to `spec.template.spec.containers`:
-
-```sh
-["tail", "-f", "/dev/null"]
-```
-
-and write the file which will update the deployment.
-
-The command `tail -f /dev/null` is the equivalent of *sleep forever*. It is a
-hack to keep the container busy and to prevent its shutdown. It will also
-override the default `neo4j` command and the kubernetes pod will not start the
-database.
-
-Now perform your tasks!
-
-When you're done, edit the deployment again and remove the `command`. Write the
-file and trigger an update of the deployment.
-
-## Create a Backup in Kubernetes
-
-First stop your Neo4J database, see above. Then:
-
-```sh
-$ kubectl -n ocelot-social get pods
-# Copy the ID of the pod running Neo4J.
-$ kubectl -n ocelot-social exec -it bash
-# Once you're in the pod, dump the db to a file e.g. `/root/neo4j-backup`.
-> neo4j-admin dump --to=/root/neo4j-backup
-> exit
-# Download the file from the pod to your computer.
-$ kubectl cp human-connection/:/root/neo4j-backup ./neo4j-backup
-```
-
-Revert your changes to deployment `develop-neo4j` which will restart the database.
-
-## Restore a Backup in Kubernetes
-
-First stop your Neo4J database. Then:
-
-```sh
-$ kubectl -n ocelot-social get pods
-# Copy the ID of the pod running Neo4J.
-# Then upload your local backup to the pod. Note that once the pod gets deleted
-# e.g. if you change the deployment, the backup file is gone with it.
-$ kubectl cp ./neo4j-backup human-connection/:/root/
-$ kubectl -n ocelot-social exec -it bash
-# Once you're in the pod restore the backup and overwrite the default database
-# called `graph.db` with `--force`.
-# This will delete all existing data in database `graph.db`!
-> neo4j-admin load --from=/root/neo4j-backup --force
-> exit
-```
-
-Revert your changes to deployment `develop-neo4j` which will restart the database.
diff --git a/deployment/volumes/neo4j-online-backup/README.md b/deployment/volumes/neo4j-online-backup/README.md
deleted file mode 100644
index 602bbd577..000000000
--- a/deployment/volumes/neo4j-online-backup/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# Backup (online)
-
-## Online backups are only avaible with a Neo4j Enterprise and a license, see https://neo4j.com/licensing/ for the different licenses available
-
-This tutorial explains how to carry out an online backup of your Neo4J
-database in a kubernetes cluster.
-
-One of the benefits of doing an online backup is that the Neo4j database does not need to be stopped, so there is no downtime. Read [the docs](https://neo4j.com/docs/operations-manual/current/backup/performing/)
-
-To use Neo4j Enterprise you must add this line to your configmap, if using, or your deployment `develop-neo4j` env.
-
-```sh
-NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes"
-```
-
-## Create a Backup in Kubernetes
-
-```sh
-# Backup the database with one command, this will get the develop-neo4j pod, ssh into it, and run the backup command
-$ kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep develop-neo4j | awk '{ print $1 }') -- neo4j-admin backup --backup-dir=/var/lib/neo4j --name=neo4j-backup
-# Download the file from the pod to your computer.
-$ kubectl cp human-connection/$(kubectl -n=human-connection get pods | grep develop-neo4j | awk '{ print $1 }'):/var/lib/neo4j/neo4j-backup ./neo4j-backup/
-```
-
-You should now have a backup of the database locally. If you want, you can simulate disaster recovery by sshing into the develop-neo4j pod, deleting all data and restoring from backup
-
-## Disaster where database data is gone somehow
-
-```sh
-$ kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep develop-neo4j |awk '{ print $1 }') bash
-# Enter cypher-shell
-$ cypher-shell
-# Delete all data
-> MATCH (n) DETACH DELETE (n);
-
-> exit
-```
-
-## Restore a backup in Kubernetes
-
-Restoration must be done while the database is not running, see [our docs](https://docs.human-connection.org/human-connection/deployment/volumes/neo4j-offline-backup#stop-and-restart-neo-4-j-database-in-kubernetes) for how to stop the database, but keep the container running
-
-After, you have stopped the database, and have the pod running, you can restore the database by running these commands:
-
-```sh
-$ kubectl -n ocelot-social get pods
-# Copy the ID of the pod running Neo4J.
-# Then upload your local backup to the pod. Note that once the pod gets deleted
-# e.g. if you change the deployment, the backup file is gone with it.
-$ kubectl cp ./neo4j-backup/ human-connection/:/root/
-$ kubectl -n ocelot-social exec -it bash
-# Once you're in the pod restore the backup and overwrite the default database
-# called `graph.db` with `--force`.
-# This will delete all existing data in database `graph.db`!
-> neo4j-admin restore --from=/root/neo4j-backup --force
-> exit
-```
-
-Revert your changes to deployment `develop-neo4j` which will restart the database.
diff --git a/deployment/volumes/reclaim-policy/README.md b/deployment/volumes/reclaim-policy/README.md
deleted file mode 100644
index ecafd3973..000000000
--- a/deployment/volumes/reclaim-policy/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# Change Reclaim Policy
-
-We recommend to change the `ReclaimPolicy`, so if you delete the persistent
-volume claims, the associated volumes will be released, not deleted.
-
-This procedure is optional and an additional security measure. It might prevent
-you from loosing data if you accidently delete the namespace and the persistent
-volumes along with it.
-
-```sh
-$ kubectl -n ocelot-social get pv
-
-NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
-pvc-bd02a715-66d0-11e9-be52-ba9c337f4551 5Gi RWO Delete Bound ocelot-social/neo4j-data-claim do-block-storage 4m24s
-pvc-bd208086-66d0-11e9-be52-ba9c337f4551 10Gi RWO Delete Bound ocelot-social/uploads-claim do-block-storage 4m12s
-```
-
-Get the volume id from above, then change `ReclaimPolicy` with:
-
-```sh
-kubectl patch pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
-
-# in the above example
-kubectl patch pv pvc-bd02a715-66d0-11e9-be52-ba9c337f4551 -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
-kubectl patch pv pvc-bd208086-66d0-11e9-be52-ba9c337f4551 -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
-```
-
-Given that you changed the reclaim policy as described above, you should be able
-to create a persistent volume claim based on a volume snapshot content. See
-the general kubernetes documentation [here](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/)
-and our specific documentation for snapshots [here](../snapshot/README.md).
diff --git a/deployment/volumes/uploads.yaml b/deployment/volumes/uploads.yaml
deleted file mode 100644
index 45e1292a8..000000000
--- a/deployment/volumes/uploads.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
----
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: uploads-claim
- namespace: ocelot-social
- spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: "10Gi"
diff --git a/deployment/volumes/velero/README.md b/deployment/volumes/velero/README.md
deleted file mode 100644
index 5b8fc9d2e..000000000
--- a/deployment/volumes/velero/README.md
+++ /dev/null
@@ -1,112 +0,0 @@
-# Velero
-
-{% hint style="danger" %}
-I tried Velero and it did not work reliably all the time. Sometimes the
-kubernetes cluster crashes during recovery or data is not fully recovered.
-
-Feel free to test it out and update this documentation once you feel that it's
-working reliably. It is very likely that Digital Ocean had some bugs when I
-tried out the steps below.
-{% endhint %}
-
-We use [velero](https://github.com/heptio/velero) for on premise backups, we
-tested on version `v0.11.0`, you can find their
-documentation [here](https://heptio.github.io/velero/v0.11.0/).
-
-Our kubernets configurations adds some annotations to pods. The annotations
-define the important persistent volumes that need to be backed up. Velero will
-pick them up and store the volumes in the same cluster but in another namespace
-`velero`.
-
-## Prequisites
-
-You have to install the binary `velero` on your computer and get a tarball of
-the latest release. We use `v0.11.0` so visit the
-[release](https://github.com/heptio/velero/releases/tag/v0.11.0) page and
-download and extract e.g. [velero-v0.11.0-linux-arm64.tar.gz](https://github.com/heptio/velero/releases/download/v0.11.0/velero-v0.11.0-linux-amd64.tar.gz).
-
-
-## Setup Velero Namespace
-
-Follow their [getting started](https://heptio.github.io/velero/v0.11.0/get-started)
-instructions to setup the Velero namespace. We use
-[Minio](https://docs.min.io/docs/deploy-minio-on-kubernetes) and
-[restic](https://github.com/restic/restic), so check out Velero's instructions
-how to setup [restic](https://heptio.github.io/velero/v0.11.0/restic):
-
-```sh
-# run from the extracted folder of the tarball
-$ kubectl apply -f config/common/00-prereqs.yaml
-$ kubectl apply -f config/minio/
-```
-
-Once completed, you should see the namespace in your kubernetes dashboard.
-
-## Manually Create an On-Premise Backup
-
-When you create your deployments for Human Connection the required annotations
-should already be in place. So when you create a backup of namespace
-`human-connection`:
-
-```sh
-$ velero backup create hc-backup --include-namespaces=human-connection
-```
-
-That should backup your persistent volumes, too. When you enter:
-
-```sh
-$ velero backup describe hc-backup --details
-```
-
-You should see the persistent volumes at the end of the log:
-
-```sh
-....
-
-Restic Backups:
- Completed:
- human-connection/develop-backend-5b6dd96d6b-q77n6: uploads
- human-connection/develop-neo4j-686d768598-z2vhh: neo4j-data
-```
-
-## Simulate a Disaster
-
-Feel free to try out if you loose any data when you simulate a disaster and try
-to restore the namespace from the backup:
-
-```sh
-$ kubectl delete namespace human-connection
-```
-
-Wait until the wrongdoing has completed, then:
-```sh
-$ velero restore create --from-backup hc-backup
-```
-
-Now, I keep my fingers crossed that everything comes back again. If not, I feel
-very sorry for you.
-
-
-## Schedule a Regular Backup
-
-Check out the [docs](https://heptio.github.io/velero/v0.11.0/get-started). You
-can create a regular schedule e.g. with:
-
-```sh
-$ velero schedule create hc-weekly-backup --schedule="@weekly" --include-namespaces=human-connection
-```
-
-Inspect the created backups:
-
-```sh
-$ velero schedule get
-NAME STATUS CREATED SCHEDULE BACKUP TTL LAST BACKUP SELECTOR
-hc-weekly-backup Enabled 2019-05-08 17:51:31 +0200 CEST @weekly 720h0m0s 6s ago
-
-$ velero backup get
-NAME STATUS CREATED EXPIRES STORAGE LOCATION SELECTOR
-hc-weekly-backup-20190508155132 Completed 2019-05-08 17:51:32 +0200 CEST 29d default
-
-$ velero backup describe hc-weekly-backup-20190508155132 --details
-# see if the persistent volumes are backed up
-```
diff --git a/deployment/volumes/volume-snapshots/README.md b/deployment/volumes/volume-snapshots/README.md
deleted file mode 100644
index cc66ae4ae..000000000
--- a/deployment/volumes/volume-snapshots/README.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# Kubernetes Volume Snapshots
-
-It is possible to backup persistent volumes through volume snapshots. This is
-especially handy if you don't want to stop the database to create an [offline
-backup](../neo4j-offline-backup/README.md) thus having a downtime.
-
-Kubernetes announced this feature in a [blog post](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/). Please make yourself familiar with it before you continue.
-
-## Create a Volume Snapshot
-
-There is an example in this folder how you can e.g. create a volume snapshot for
-the persistent volume claim `neo4j-data-claim`:
-
-```sh
-# in folder deployment/volumes/volume-snapshots/
-kubectl apply -f snapshot.yaml
-```
-
-If you are on Digital Ocean the volume snapshot should show up in the Web UI:
-
-
-
-## Provision a Volume based on a Snapshot
-
-Edit your persistent volume claim configuration and add a `dataSource` pointing
-to your volume snapshot. [The blog post](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/) has an example in section "Provision a new volume from a snapshot with
-Kubernetes".
-
-There is also an example in this folder how the configuration could look like.
-If you apply the configuration new persistent volume claim will be provisioned
-with the data from the volume snapshot:
-
-```
-# in folder deployment/volumes/volume-snapshots/
-kubectl apply -f neo4j-data.yaml
-```
-
-## Data Consistency Warning
-
-Note that volume snapshots do not guarantee data consistency. Quote from the
-[blog post](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/):
-
-> Please note that the alpha release of Kubernetes Snapshot does not provide
-> any consistency guarantees. You have to prepare your application (pause
-> application, freeze filesystem etc.) before taking the snapshot for data
-> consistency.
-
-In case of Neo4J this probably means that enterprise edition is required which
-supports [online backups](https://neo4j.com/docs/operations-manual/current/backup/).
-
diff --git a/deployment/volumes/volume-snapshots/digital-ocean-volume-snapshots.png b/deployment/volumes/volume-snapshots/digital-ocean-volume-snapshots.png
deleted file mode 100644
index cb6599616..000000000
Binary files a/deployment/volumes/volume-snapshots/digital-ocean-volume-snapshots.png and /dev/null differ
diff --git a/deployment/volumes/volume-snapshots/neo4j-data.yaml b/deployment/volumes/volume-snapshots/neo4j-data.yaml
deleted file mode 100644
index cd8552bda..000000000
--- a/deployment/volumes/volume-snapshots/neo4j-data.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
----
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: neo4j-data-claim
- namespace: ocelot-social
- labels:
- app: ocelot-social
- spec:
- dataSource:
- name: neo4j-data-snapshot
- kind: VolumeSnapshot
- apiGroup: snapshot.storage.k8s.io
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: 1Gi
diff --git a/deployment/volumes/volume-snapshots/snapshot.yaml b/deployment/volumes/volume-snapshots/snapshot.yaml
deleted file mode 100644
index eac2f0857..000000000
--- a/deployment/volumes/volume-snapshots/snapshot.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
----
- apiVersion: snapshot.storage.k8s.io/v1alpha1
- kind: VolumeSnapshot
- metadata:
- name: uploads-snapshot
- namespace: ocelot-social
- spec:
- source:
- name: uploads-claim
- kind: PersistentVolumeClaim
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
index 8d1bb96f2..5fceb2776 100644
--- a/docker-compose.override.yml
+++ b/docker-compose.override.yml
@@ -18,7 +18,6 @@ services:
- webapp_node_modules:/app/node_modules
# bind the local folder to the docker to allow live reload
- ./webapp:/app
-
########################################################
# BACKEND ##############################################
########################################################
@@ -35,7 +34,6 @@ services:
- backend_node_modules:/app/node_modules
# bind the local folder to the docker to allow live reload
- ./backend:/app
-
########################################################
# NEO4J ################################################
########################################################
@@ -47,13 +45,11 @@ services:
networks:
# So we can access the neo4j query browser from our host machine
- external-net
-
########################################################
# MAINTENANCE ##########################################
########################################################
maintenance:
image: ocelotsocialnetwork/maintenance:development
-
########################################################
# MAILSERVER TO FAKE SMTP ##############################
########################################################
@@ -63,6 +59,7 @@ services:
- 1080:80
networks:
- external-net
+
volumes:
webapp_node_modules:
backend_node_modules:
diff --git a/docker-compose.test.yml b/docker-compose.test.yml
index 2d382d606..973cf87cf 100644
--- a/docker-compose.test.yml
+++ b/docker-compose.test.yml
@@ -10,7 +10,8 @@ services:
target: test
environment:
- NODE_ENV="test"
-
+ volumes:
+ - ./coverage:/app/coverage
########################################################
# BACKEND ##############################################
########################################################
@@ -20,19 +21,29 @@ services:
target: test
environment:
- NODE_ENV="test"
-
+ volumes:
+ - ./coverage:/app/coverage
########################################################
# NEO4J ################################################
########################################################
neo4j:
image: ocelotsocialnetwork/neo4j:community
-
+ #environment:
+ # - NEO4J_dbms_connector_bolt_enabled=true
+ # - NEO4J_dbms_connector_bolt_tls__level=OPTIONAL
+ # - NEO4J_dbms_connector_bolt_listen__address=0.0.0.0:7687
+ # - NEO4J_auth=none
+ # - NEO4J_dbms_connectors_default__listen__address=0.0.0.0
+ # - NEO4J_dbms_connector_http_listen__address=0.0.0.0:7474
+ # - NEO4J_dbms_connector_https_listen__address=0.0.0.0:7473
+ networks:
+ # So we can access the neo4j query browser from our host machine
+ - external-net
########################################################
# MAINTENANCE ##########################################
########################################################
maintenance:
image: ocelotsocialnetwork/maintenance:test
-
########################################################
# MAILSERVER TO FAKE SMTP ##############################
########################################################
@@ -42,6 +53,7 @@ services:
- 1080:80
networks:
- external-net
+
volumes:
webapp_node_modules:
backend_node_modules:
diff --git a/docker-compose.yml b/docker-compose.yml
index 392447f61..d20bb6aec 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -35,7 +35,6 @@ services:
- GRAPHQL_URI=http://backend:4000
env_file:
- ./webapp/.env
-
########################################################
# BACKEND ##############################################
########################################################
@@ -68,7 +67,6 @@ services:
- CLIENT_URI=http://webapp:3000
env_file:
- ./backend/.env
-
########################################################
# NEO4J ################################################
########################################################
@@ -92,7 +90,6 @@ services:
# TODO: clarify if that is the only thing needed to unlock the Enterprise version
# - NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
# TODO: Remove the playground from production
-
########################################################
# MAINTENANCE ##########################################
########################################################
diff --git a/neo4j/Dockerfile b/neo4j/Dockerfile
index e7931378a..4bdc4ef1f 100644
--- a/neo4j/Dockerfile
+++ b/neo4j/Dockerfile
@@ -3,13 +3,16 @@
##################################################################################
FROM neo4j:3.5.14 as community
-# ENVs (available in production aswell, can be overwritten by commandline or env file)
+# ENVs
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
-ENV BUILD_DATE="1970-01-01T00:00:00.00Z"
+ARG BBUILD_DATE="1970-01-01T00:00:00.00Z"
+ENV BUILD_DATE=$BBUILD_DATE
## We cannot do $(yarn run version)-${BUILD_NUMBER} here so we default to 0.0.0-0
-ENV BUILD_VERSION="0.0.0-0"
+ARG BBUILD_VERSION="0.0.0-0"
+ENV BUILD_VERSION=$BBUILD_VERSION
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
-ENV BUILD_COMMIT="0000000"
+ARG BBUILD_COMMIT="0000000"
+ENV BUILD_COMMIT=$BBUILD_COMMIT
# Labels
LABEL org.label-schema.build-date="${BUILD_DATE}"
diff --git a/package.json b/package.json
index fbf890306..077da6f36 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ocelot-social",
- "version": "0.6.15",
+ "version": "1.0.4",
"description": "Fullstack and API tests with cypress and cucumber for ocelot.social",
"author": "ocelot.social Community",
"license": "MIT",
@@ -13,14 +13,10 @@
"nonGlobalStepDefinitions": true
},
"scripts": {
- "install:all": "yarn install && cd backend && yarn install && cd ../webapp && yarn install",
"db:seed": "cd backend && yarn run db:seed",
"db:reset": "cd backend && yarn run db:reset",
- "cypress:backend": "cd backend && yarn run dev",
- "cypress:webapp": "cd webapp && yarn run dev",
- "cypress:setup": "run-p cypress:backend cypress:webapp",
- "cypress:run": "cross-env cypress run --browser firefox",
- "cypress:open": "cross-env cypress open --browser firefox",
+ "cypress:run": "cypress run --browser electron --config-file ./cypress/cypress.json",
+ "cypress:open": "cypress open --browser electron --config-file ./cypress/cypress.json",
"cucumber:setup": "cd backend && yarn run dev",
"cucumber": "wait-on tcp:4000 && cucumber-js --require-module @babel/register --exit",
"release": "yarn version --no-git-tag-version --no-commit-hooks --no-commit && auto-changelog --latest-version $(node -p -e \"require('./package.json').version\") && cd backend && yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version $(node -p -e \"require('./../package.json').version\") && cd ../webapp && yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version $(node -p -e \"require('./../package.json').version\")"
@@ -32,27 +28,27 @@
"auto-changelog": "^2.2.1",
"bcryptjs": "^2.4.3",
"codecov": "^3.7.1",
- "cross-env": "^7.0.2",
+ "cross-env": "^7.0.3",
"cucumber": "^6.0.5",
- "cypress": "^4.2.0",
+ "cypress": "^7.0.1",
"cypress-cucumber-preprocessor": "^2.2.1",
"cypress-file-upload": "^3.5.3",
- "cypress-plugin-retries": "^1.5.2",
- "date-fns": "^2.12.0",
+ "date-fns": "^2.22.1",
"dotenv": "^8.2.0",
"expect": "^25.3.0",
"faker": "Marak/faker.js#master",
- "graphql-request": "^1.8.2",
+ "graphql-request": "^2.0.0",
"import": "^0.0.6",
"jsonwebtoken": "^8.5.1",
"mock-socket": "^9.0.3",
"neo4j-driver": "^4.0.2",
- "neode": "^0.3.7",
+ "neode": "^0.4.7",
"npm-run-all": "^4.1.5",
"rosie": "^2.0.1",
"slug": "^2.1.1"
},
"resolutions": {
"set-value": "^2.0.1"
- }
+ },
+ "dependencies": {}
}
diff --git a/scripts/.gitkeep b/scripts/.gitkeep
new file mode 100644
index 000000000..b0699a3b8
--- /dev/null
+++ b/scripts/.gitkeep
@@ -0,0 +1 @@
+We will put CI and package.json scripts in here in the future
\ No newline at end of file
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
deleted file mode 100755
index c79223c69..000000000
--- a/scripts/deploy.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-sed -i "s//${TRAVIS_COMMIT}/g" $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml
-sed -i "s//${TRAVIS_COMMIT}/g" $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml
-kubectl -n ocelot-social patch configmap develop-configmap -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml)"
-kubectl -n ocelot-social patch deployment backend -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)"
-kubectl -n ocelot-social patch deployment webapp -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)"
diff --git a/scripts/patches/patch-configmap.yaml b/scripts/patches/patch-configmap.yaml
deleted file mode 100644
index a77c567fa..000000000
--- a/scripts/patches/patch-configmap.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-data:
- COMMIT:
-metadata:
- name: configmap
- namespace: ocelot-social
diff --git a/scripts/patches/patch-deployment.yaml b/scripts/patches/patch-deployment.yaml
deleted file mode 100644
index 7c46eb8b0..000000000
--- a/scripts/patches/patch-deployment.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-spec:
- template:
- metadata:
- labels:
- ocelot.social/commit:
diff --git a/scripts/setup_kubernetes.sh b/scripts/setup_kubernetes.sh
deleted file mode 100755
index ea39312f6..000000000
--- a/scripts/setup_kubernetes.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env bash
-
-# This script can be called multiple times for each `before_deploy` hook
-# so let's exit successfully if kubectl is already installed:
-command -v kubectl && exit 0
-
-curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
-chmod +x ./kubectl
-sudo mv ./kubectl /usr/local/bin/kubectl
-
-curl -LO https://github.com/digitalocean/doctl/releases/download/v1.14.0/doctl-1.14.0-linux-amd64.tar.gz
-tar xf doctl-1.14.0-linux-amd64.tar.gz
-chmod +x ./doctl
-sudo mv ./doctl /usr/local/bin/doctl
-
-doctl auth init --access-token $DIGITALOCEAN_ACCESS_TOKEN
-mkdir -p ~/.kube/
-doctl k8s cluster kubeconfig show develop > ~/.kube/config
diff --git a/webapp/.env.template b/webapp/.env.template
index 1acad49b4..7373255a9 100644
--- a/webapp/.env.template
+++ b/webapp/.env.template
@@ -1,5 +1,6 @@
SENTRY_DSN_WEBAPP=
COMMIT=
PUBLIC_REGISTRATION=false
+INVITE_REGISTRATION=true
WEBSOCKETS_URI=ws://localhost:3000/api/graphql
GRAPHQL_URI=http://localhost:4000/
diff --git a/webapp/Dockerfile b/webapp/Dockerfile
index eda437920..8d830a9d5 100644
--- a/webapp/Dockerfile
+++ b/webapp/Dockerfile
@@ -1,17 +1,20 @@
##################################################################################
-# BASE ###########################################################################
+# BASE (Is pushed to DockerHub for rebranding) ###################################
##################################################################################
FROM node:12.19.0-alpine3.10 as base
-# ENVs (available in production aswell, can be overwritten by commandline or env file)
+# ENVs
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
ENV DOCKER_WORKDIR="/app"
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
-ENV BUILD_DATE="1970-01-01T00:00:00.00Z"
+ARG BBUILD_DATE="1970-01-01T00:00:00.00Z"
+ENV BUILD_DATE=$BBUILD_DATE
## We cannot do $(yarn run version)-${BUILD_NUMBER} here so we default to 0.0.0-0
-ENV BUILD_VERSION="0.0.0-0"
+ARG BBUILD_VERSION="0.0.0-0"
+ENV BUILD_VERSION=$BBUILD_VERSION
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
-ENV BUILD_COMMIT="0000000"
+ARG BBUILD_COMMIT="0000000"
+ENV BUILD_COMMIT=$BBUILD_COMMIT
## SET NODE_ENV
ENV NODE_ENV="production"
## App relevant Envs
@@ -56,12 +59,18 @@ FROM base as development
CMD /bin/sh -c "yarn install && yarn run dev"
##################################################################################
-# BUILD (Does contain all files and is therefore bloated) ########################
+# CODE (Does contain all code files and is pushed to DockerHub for rebranding) ###
##################################################################################
-FROM base as build
+FROM base as code
-# Copy everything
+# copy everything, but do not build.
COPY . .
+
+##################################################################################
+# BUILD (Does contain all files and the compilate and is therefore bloated) ######
+##################################################################################
+FROM code as build
+
# yarn install
RUN yarn install --production=false --frozen-lockfile --non-interactive
# yarn build
@@ -80,18 +89,13 @@ CMD /bin/sh -c "yarn run dev"
##################################################################################
FROM base as production
+# TODO - do all copying with one COPY command to have one layer
# Copy "binary"-files from build image
COPY --from=build ${DOCKER_WORKDIR}/.nuxt ./.nuxt
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
COPY --from=build ${DOCKER_WORKDIR}/nuxt.config.js ./nuxt.config.js
-# Copy static files
-# TODO - this should be one Folder containign all stuff needed to be copied
-COPY --from=build ${DOCKER_WORKDIR}/config/ ./config/
-COPY --from=build ${DOCKER_WORKDIR}/constants ./constants
-COPY --from=build ${DOCKER_WORKDIR}/static ./static
-COPY --from=build ${DOCKER_WORKDIR}/locales ./locales
# Copy package.json for script definitions (lock file should not be needed)
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
# Run command
-CMD /bin/sh -c "yarn run start"
\ No newline at end of file
+CMD /bin/sh -c "yarn run start"
diff --git a/webapp/Dockerfile.maintenance b/webapp/Dockerfile.maintenance
index b02fe352b..dcc06ad61 100644
--- a/webapp/Dockerfile.maintenance
+++ b/webapp/Dockerfile.maintenance
@@ -1,20 +1,59 @@
-FROM node:12.19.0-alpine3.10 as build
-LABEL Description="Maintenance page of the Social Network ocelot.social" Vendor="ocelot.social Community" Version="0.0.1" Maintainer="ocelot.social Community (devops@ocelot.social)"
+##################################################################################
+# BASE ###########################################################################
+##################################################################################
+FROM node:12.19.0-alpine3.10 as base
-EXPOSE 3000
-CMD ["yarn", "run", "start"]
+# ENVs
+## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
+ENV DOCKER_WORKDIR="/app"
+## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
+ARG BBUILD_DATE="1970-01-01T00:00:00.00Z"
+ENV BUILD_DATE=$BBUILD_DATE
+## We cannot do $(yarn run version)-${BUILD_NUMBER} here so we default to 0.0.0-0
+ARG BBUILD_VERSION="0.0.0-0"
+ENV BUILD_VERSION=$BBUILD_VERSION
+## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
+ARG BBUILD_COMMIT="0000000"
+ENV BUILD_COMMIT=$BBUILD_COMMIT
+## SET NODE_ENV
+ENV NODE_ENV="production"
+## App relevant Envs
+ENV PORT="3000"
-# Expose the app port
-ARG BUILD_COMMIT
-ENV BUILD_COMMIT=$BUILD_COMMIT
-ARG WORKDIR=/develop-webapp
-RUN mkdir -p $WORKDIR
-WORKDIR $WORKDIR
+# Labels
+LABEL org.label-schema.build-date="${BUILD_DATE}"
+LABEL org.label-schema.name="ocelot.social:backend"
+LABEL org.label-schema.description="Maintenance page of the Social Network Software ocelot.social"
+LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md"
+LABEL org.label-schema.url="https://ocelot.social"
+LABEL org.label-schema.vcs-url="https://github.com/Ocelot-Social-Community/Ocelot-Social/tree/master/backend"
+LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
+LABEL org.label-schema.vendor="ocelot.social Community"
+LABEL org.label-schema.version="${BUILD_VERSION}"
+LABEL org.label-schema.schema-version="1.0"
+LABEL maintainer="devops@ocelot.social"
-# See: https://github.com/nodejs/docker-node/pull/367#issuecomment-430807898
+# Install Additional Software
+## install: git
RUN apk --no-cache add git
+# Settings
+## Expose Container Port
+EXPOSE ${PORT}
+
+## Workdir
+RUN mkdir -p ${DOCKER_WORKDIR}
+WORKDIR ${DOCKER_WORKDIR}
+
+CMD ["yarn", "run", "start"]
+
+##################################################################################
+# CODE (Does contain all code files and is pushed to DockerHub for rebranding) ###
+##################################################################################
+FROM base as code
+
COPY package.json yarn.lock ./
+# yarn install
RUN yarn install --production=false --frozen-lockfile --non-interactive
COPY assets assets
@@ -29,14 +68,24 @@ COPY constants constants
COPY nuxt.config.js nuxt.config.js
COPY config/ config/
+# this is needed in rebranding
+COPY maintenance/nginx maintenance/nginx
# this will also ovewrite the existing package.json
COPY maintenance/source ./
+##################################################################################
+# BUILD ### TODO # TODO # TODO # TODO # TODO # TODO # TODO # TODO # TODO # TODO ##
+##################################################################################
+FROM code as build
+# yarn generate
RUN yarn run generate
+##################################################################################
+# PRODUCTION ### TODO # TODO # TODO # TODO # TODO # TODO # TODO # TODO # TODO ####
+##################################################################################
+FROM nginx:alpine as production
-FROM nginx:alpine
-COPY --from=build ./develop-webapp/dist/ /usr/share/nginx/html/
+COPY --from=build ./app/dist/ /usr/share/nginx/html/
RUN rm /etc/nginx/conf.d/default.conf
COPY maintenance/nginx/custom.conf /etc/nginx/conf.d/
diff --git a/webapp/assets/_new/icons/svgs/copy.svg b/webapp/assets/_new/icons/svgs/copy.svg
new file mode 100644
index 000000000..1792f2002
--- /dev/null
+++ b/webapp/assets/_new/icons/svgs/copy.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/webapp/components/AvatarMenu/AvatarMenu.vue b/webapp/components/AvatarMenu/AvatarMenu.vue
index f65c6f6cf..d47eb2d68 100644
--- a/webapp/components/AvatarMenu/AvatarMenu.vue
+++ b/webapp/components/AvatarMenu/AvatarMenu.vue
@@ -46,6 +46,7 @@
+
+
diff --git a/webapp/components/Editor/Editor.spec.js b/webapp/components/Editor/Editor.spec.js
index ee762b332..f51c5782f 100644
--- a/webapp/components/Editor/Editor.spec.js
+++ b/webapp/components/Editor/Editor.spec.js
@@ -99,6 +99,37 @@ describe('Editor.vue', () => {
})
})
+ it('suggestion list returns results prefixed by query', () => {
+ const manyUsersList = []
+ for (let i = 0; i < 10; i++) {
+ manyUsersList.push({ id: `user${i}` })
+ manyUsersList.push({ id: `admin${i}` })
+ manyUsersList.push({ id: `moderator${i}` })
+ }
+ propsData.users = manyUsersList
+ wrapper = Wrapper()
+ const suggestionList = wrapper.vm.editor.extensions.options.mention.onFilter(
+ propsData.users,
+ 'moderator',
+ )
+ expect(suggestionList).toHaveLength(10)
+ for (var i = 0; i < suggestionList.length; i++) {
+ expect(suggestionList[i].id).toMatch(/^moderator.*/)
+ }
+ })
+
+ it('exact match appears at the top of suggestion list', () => {
+ const manyUsersList = []
+ for (let i = 0; i < 25; i++) {
+ manyUsersList.push({ id: `user${i}` })
+ }
+ propsData.users = manyUsersList
+ wrapper = Wrapper()
+ expect(
+ wrapper.vm.editor.extensions.options.mention.onFilter(propsData.users, 'user7')[0].id,
+ ).toMatch('user7')
+ })
+
it('sets the Hashtag items to the hashtags', () => {
propsData.hashtags = [
{
diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue
index 67329a60b..cf0fd710b 100644
--- a/webapp/components/Editor/Editor.vue
+++ b/webapp/components/Editor/Editor.vue
@@ -185,6 +185,9 @@ export default {
if (this.suggestionType === HASHTAG && this.query !== '') {
this.selectItem({ id: this.query })
}
+ if (this.suggestionType === MENTION && item) {
+ this.selectItem(item)
+ }
return true
default:
@@ -199,9 +202,14 @@ export default {
const filteredList = items.filter((item) => {
const itemString = item.slug || item.id
- return itemString.toLowerCase().includes(query.toLowerCase())
+ return itemString.toLowerCase().startsWith(query.toLowerCase())
})
- return filteredList.slice(0, 15)
+ const sortedList = filteredList.sort((itemA, itemB) => {
+ const aString = itemA.slug || itemA.id
+ const bString = itemB.slug || itemB.id
+ return aString.length - bString.length
+ })
+ return sortedList.slice(0, 15)
},
sanitizeQuery(query) {
if (this.suggestionType === HASHTAG) {
diff --git a/webapp/components/Editor/MenuBar.vue b/webapp/components/Editor/MenuBar.vue
index 3756e1ae2..b64cf779e 100644
--- a/webapp/components/Editor/MenuBar.vue
+++ b/webapp/components/Editor/MenuBar.vue
@@ -59,6 +59,8 @@
:onClick="commands.horizontal_rule"
icon="minus"
/>
+
+
@@ -66,11 +68,13 @@
+
+
diff --git a/webapp/components/Editor/MenuBarButton.vue b/webapp/components/Editor/MenuBarButton.vue
index 2543352ca..e4f11e46d 100644
--- a/webapp/components/Editor/MenuBarButton.vue
+++ b/webapp/components/Editor/MenuBarButton.vue
@@ -10,7 +10,7 @@ export default {
isActive: Boolean,
icon: String,
label: String,
- onClick: Function,
+ onClick: { type: Function, default: () => {} },
},
}
diff --git a/webapp/components/Editor/MenuLegend.vue b/webapp/components/Editor/MenuLegend.vue
new file mode 100644
index 000000000..afc137b1f
--- /dev/null
+++ b/webapp/components/Editor/MenuLegend.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+