diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ac333604e..d4ecca7f5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,8 +3,8 @@ name: ocelot.social publish CI on: push: branches: - - master - # - 5093-fix-automatic-deployment # for testing while developing + # - master + - 5059-epic-groups # for testing while developing jobs: ############################################################################## @@ -94,16 +94,16 @@ jobs: ########################################################################## - name: Backend | Build `production` image run: | - docker build --target base -t "ocelotsocialnetwork/backend:latest-base" -t "ocelotsocialnetwork/backend:${VERSION}-base" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/ - docker build --target code -t "ocelotsocialnetwork/backend:latest-code" -t "ocelotsocialnetwork/backend:${VERSION}-code" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/ - docker build --target production -t "ocelotsocialnetwork/backend:latest" -t "ocelotsocialnetwork/backend:${VERSION}" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/ + docker build --target base -t "ocelotsocialnetwork/backend-groups:latest-base" -t "ocelotsocialnetwork/backend-groups:${VERSION}-base" -t "ocelotsocialnetwork/backend-groups:${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-groups:latest-code" -t "ocelotsocialnetwork/backend-groups:${VERSION}-code" -t "ocelotsocialnetwork/backend-groups:${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-groups:latest" -t "ocelotsocialnetwork/backend-groups:${VERSION}" -t "ocelotsocialnetwork/backend-groups:${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 + run: docker save "ocelotsocialnetwork/backend-groups" > /tmp/backend-groups.tar - name: Upload Artifact uses: actions/upload-artifact@v2 with: name: docker-backend-production - path: /tmp/backend.tar + path: /tmp/backend-groups.tar ############################################################################## # JOB: DOCKER BUILD PRODUCTION WEBAPP ######################################## @@ -134,16 +134,16 @@ jobs: ########################################################################## - name: Webapp | Build `production` image run: | - docker build --target base -t "ocelotsocialnetwork/webapp:latest-base" -t "ocelotsocialnetwork/webapp:${VERSION}-base" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ - docker build --target code -t "ocelotsocialnetwork/webapp:latest-code" -t "ocelotsocialnetwork/webapp:${VERSION}-code" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ - docker build --target production -t "ocelotsocialnetwork/webapp:latest" -t "ocelotsocialnetwork/webapp:${VERSION}" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ + docker build --target base -t "ocelotsocialnetwork/webapp-groups:latest-base" -t "ocelotsocialnetwork/webapp-groups:${VERSION}-base" -t "ocelotsocialnetwork/webapp-groups:${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-groups:latest-code" -t "ocelotsocialnetwork/webapp-groups:${VERSION}-code" -t "ocelotsocialnetwork/webapp-groups:${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-groups:latest" -t "ocelotsocialnetwork/webapp-groups:${VERSION}" -t "ocelotsocialnetwork/webapp-groups:${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 + run: docker save "ocelotsocialnetwork/webapp-groups" > /tmp/webapp-groups.tar - name: Upload Artifact uses: actions/upload-artifact@v2 with: name: docker-webapp-production - path: /tmp/webapp.tar + path: /tmp/webapp-groups.tar ############################################################################## # JOB: DOCKER BUILD PRODUCTION MAINTENANCE ################################### @@ -174,16 +174,16 @@ jobs: ########################################################################## - name: Maintenance | Build `production` image run: | - docker build --target base -t "ocelotsocialnetwork/maintenance:latest-base" -t "ocelotsocialnetwork/maintenance:${VERSION}-base" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance - docker build --target code -t "ocelotsocialnetwork/maintenance:latest-code" -t "ocelotsocialnetwork/maintenance:${VERSION}-code" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance - docker build --target production -t "ocelotsocialnetwork/maintenance:latest" -t "ocelotsocialnetwork/maintenance:${VERSION}" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance + docker build --target base -t "ocelotsocialnetwork/maintenance-groups:latest-base" -t "ocelotsocialnetwork/maintenance-groups:${VERSION}-base" -t "ocelotsocialnetwork/maintenance-groups:${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-groups:latest-code" -t "ocelotsocialnetwork/maintenance-groups:${VERSION}-code" -t "ocelotsocialnetwork/maintenance-groups:${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-groups:latest" -t "ocelotsocialnetwork/maintenance-groups:${VERSION}" -t "ocelotsocialnetwork/maintenance-groups:${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 + run: docker save "ocelotsocialnetwork/maintenance-groups" > /tmp/maintenance-groups.tar - name: Upload Artifact uses: actions/upload-artifact@v2 with: name: docker-maintenance-production - path: /tmp/maintenance.tar + path: /tmp/maintenance-groups.tar ############################################################################## # JOB: UPLOAD TO DOCKERHUB ################################################### @@ -217,21 +217,21 @@ jobs: name: docker-backend-production path: /tmp - name: Load Docker Image - run: docker load < /tmp/backend.tar + run: docker load < /tmp/backend-groups.tar - name: Download Docker Image (WebApp) uses: actions/download-artifact@v2 with: name: docker-webapp-production path: /tmp - name: Load Docker Image - run: docker load < /tmp/webapp.tar + run: docker load < /tmp/webapp-groups.tar - name: Download Docker Image (Maintenance) uses: actions/download-artifact@v2 with: name: docker-maintenance-production path: /tmp - name: Load Docker Image - run: docker load < /tmp/maintenance.tar + run: docker load < /tmp/maintenance-groups.tar ########################################################################## # Upload ################################################################# ########################################################################## @@ -240,11 +240,11 @@ jobs: - name: Push neo4j run: docker push --all-tags ocelotsocialnetwork/neo4j-community - name: Push backend - run: docker push --all-tags ocelotsocialnetwork/backend + run: docker push --all-tags ocelotsocialnetwork/backend-groups - name: Push webapp - run: docker push --all-tags ocelotsocialnetwork/webapp + run: docker push --all-tags ocelotsocialnetwork/webapp-groups - name: Push maintenance - run: docker push --all-tags ocelotsocialnetwork/maintenance + run: docker push --all-tags ocelotsocialnetwork/maintenance-groups ############################################################################## # JOB: KUBERNETES DEPLOY ACTUAL/LATEST VERSION ###################################### @@ -292,11 +292,11 @@ jobs: # kubectl -n default rollout restart deployment/ocelot-neo4j - name: Deploy actual version '$BUILD_VERSION' to DigitalOcean Kubernetes run: | - kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp:$BUILD_VERSION + kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp-groups:$BUILD_VERSION kubectl -n default rollout restart deployment/ocelot-webapp - kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend:$BUILD_VERSION + kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend-groups:$BUILD_VERSION kubectl -n default rollout restart deployment/ocelot-backend - kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance:$BUILD_VERSION + kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance-groups:$BUILD_VERSION kubectl -n default rollout restart deployment/ocelot-maintenance kubectl -n default set image deployment/ocelot-neo4j container-ocelot-neo4j=ocelotsocialnetwork/neo4j-community:$BUILD_VERSION kubectl -n default rollout restart deployment/ocelot-neo4j diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f2c1dcc..940cc77a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,50 @@ 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). -#### [1.1.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.8...1.1.0) +#### [1.1.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.1.0...1.1.1) +- chore: 🍰 Refactor Rebranding [`#5390`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5390) +- feat: 🍰 Tooltips For Topics [`#5350`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5350) +- feat: 🍰 Save Categories In Frontend [`#5284`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5284) +- feat: 🍰 Add New Yunite Icons [`#5319`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5319) +- chore: 🍰 Update Neode From v^0.4.7 To v^0.4.8 In Backend [`#5334`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5334) +- fix: Category Filter Menu Client Only [`#5301`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5301) +- feat: Save Category Settings [`#5261`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5261) +- feat: Topics Menu [`#5248`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5248) +- docs: 🍰 Document GraqhQL Playground [`#5253`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5253) +- feat: Categories Filter Menu [`#5198`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5198) +- fix: 🍰 Fix Test Description From `enter-nonce.vue` To `change-password` [`#5217`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5217) +- Bump cookie-universal-nuxt from 2.1.5 to 2.2.2 in /webapp [`#5218`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5218) +- Bump prettier from 2.2.1 to 2.7.1 in /webapp [`#5170`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5170) +- Bump eslint-plugin-prettier from 3.1.2 to 3.4.1 in /backend [`#5211`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5211) +- Bump slug from 4.0.2 to 6.0.0 in /backend [`#5193`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5193) +- chore: 🍰 Fix typo in PULL_REQUEST_TEMPLATE.md file [`#5208`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5208) +- Bump slug from 5.1.0 to 6.0.0 [`#5191`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5191) +- Bump vue-sweetalert-icons from 4.3.0 to 4.3.1 in /webapp [`#5174`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5174) +- feat: 🍰 Change Error Message With `Authorised` To `Authorized` All Over The Place To Have American English [`#5206`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5206) +- Bump cross-env from 7.0.2 to 7.0.3 in /webapp [`#5168`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5168) +- chore: 🍰 Add `--logHeapUsage` To Jest Test Call [`#5182`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5182) +- refactor: 🍰 Rename `UserGroup` To `UserRole` [`#5143`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5143) +- add new yunite icons [`bb0d632`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/bb0d6329e7e36ea03671318ea8dd128a6d5a5a7a) +- cleanup refactor rebranding [`5f5c0fa`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5f5c0faa1f28cd4df7681eba335ae5998b2d9cca) +- change color and scss in branding [`52070b8`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/52070b8c570970bf48df561134bf67cb4111b640) + +#### [1.1.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.9...1.1.0) + +> 4 August 2022 + +- chore: 🍰 Release v1.1.0 - Implement Categories Again [`#5145`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5145) - feat: Make Categories Optional [`#5102`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5102) - Update issue templates [`#5101`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5101) - chore: 🍰 Betters Automatic Deployment To `stage.ocelot.social` On Push To `master` Branch [`#5097`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5097) +- add optional categories to teaser and post [`bc95500`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/bc955003f7c33aabe592bee782aca973b4f00cba) +- env vatiable for CATEGORIES_ACTIVE and switch for categories in contribution form [`e31f250`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/e31f250ea5e1949f4f08e72fe82622d41ecd85f1) +- fix some tests [`5393c2a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5393c2aeaaf070a637390c430d5f03057030ff52) + +#### [1.0.9](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.8...1.0.9) + +> 20 July 2022 + - chore: 🍰 Release v1.0.9 [`#5095`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5095) - chore: 🍰 Automatic Deployment To `stage.ocelot.social` On Push To `master` Branch [`#5080`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5080) - change footer version-link [`#5091`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5091) diff --git a/backend/package.json b/backend/package.json index f6299ee48..bcc91a25a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social-backend", - "version": "1.1.0", + "version": "1.1.1", "description": "GraphQL Backend for ocelot.social", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "author": "ocelot.social Community", diff --git a/backend/src/constants/groups.js b/backend/src/constants/groups.js index 64ffeaa59..e9c941a89 100644 --- a/backend/src/constants/groups.js +++ b/backend/src/constants/groups.js @@ -1,3 +1,3 @@ // this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js` export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 100 // with removed HTML tags -export const DESCRIPTION_EXCERPT_HTML_LENGTH = 120 // with removed HTML tags +export const DESCRIPTION_EXCERPT_HTML_LENGTH = 250 // with removed HTML tags diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 4350e19c9..e388b2cd9 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -2,170 +2,202 @@ import gql from 'graphql-tag' // ------ mutations -export const createGroupMutation = gql` - mutation ( - $id: ID - $name: String! - $slug: String - $about: String - $description: String! - $groupType: GroupType! - $actionRadius: GroupActionRadius! - $categoryIds: [ID] - $locationName: String - ) { - CreateGroup( - id: $id - name: $name - slug: $slug - about: $about - description: $description - groupType: $groupType - actionRadius: $actionRadius - categoryIds: $categoryIds - locationName: $locationName +export const createGroupMutation = () => { + return gql` + mutation ( + $id: ID + $name: String! + $slug: String + $about: String + $description: String! + $groupType: GroupType! + $actionRadius: GroupActionRadius! + $categoryIds: [ID] + $locationName: String # empty string '' sets it to null ) { - id - name - slug - createdAt - updatedAt - disabled - deleted - about - description - groupType - actionRadius - categories { + CreateGroup( + id: $id + name: $name + slug: $slug + about: $about + description: $description + groupType: $groupType + actionRadius: $actionRadius + categoryIds: $categoryIds + locationName: $locationName + ) { id - slug name - icon + slug + createdAt + updatedAt + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + locationName + location { + name + nameDE + nameEN + } + myRole } - # locationName # test this as result - myRole } - } -` + ` +} -export const updateGroupMutation = gql` - mutation ( - $id: ID! - $name: String - $slug: String - $about: String - $description: String - $actionRadius: GroupActionRadius - $categoryIds: [ID] - $avatar: ImageInput - $locationName: String - ) { - UpdateGroup( - id: $id - name: $name - slug: $slug - about: $about - description: $description - actionRadius: $actionRadius - categoryIds: $categoryIds - avatar: $avatar - locationName: $locationName +export const updateGroupMutation = () => { + return gql` + mutation ( + $id: ID! + $name: String + $slug: String + $about: String + $description: String + $actionRadius: GroupActionRadius + $categoryIds: [ID] + $avatar: ImageInput + $locationName: String # empty string '' sets it to null ) { - id - name - slug - createdAt - updatedAt - disabled - deleted - about - description - groupType - actionRadius - categories { + UpdateGroup( + id: $id + name: $name + slug: $slug + about: $about + description: $description + actionRadius: $actionRadius + categoryIds: $categoryIds + avatar: $avatar + locationName: $locationName + ) { id - slug name - icon + slug + createdAt + updatedAt + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + # avatar # test this as result + locationName + location { + name + nameDE + nameEN + } + myRole } - # avatar # test this as result - # locationName # test this as result - myRole } - } -` + ` +} -export const joinGroupMutation = gql` - mutation ($groupId: ID!, $userId: ID!) { - JoinGroup(groupId: $groupId, userId: $userId) { - id - name - slug - myRoleInGroup +export const joinGroupMutation = () => { + return gql` + mutation ($groupId: ID!, $userId: ID!) { + JoinGroup(groupId: $groupId, userId: $userId) { + id + name + slug + myRoleInGroup + } } - } -` + ` +} -export const leaveGroupMutation = gql` - mutation ($groupId: ID!, $userId: ID!) { - LeaveGroup(groupId: $groupId, userId: $userId) { - id - name - slug - myRoleInGroup +export const leaveGroupMutation = () => { + return gql` + mutation ($groupId: ID!, $userId: ID!) { + LeaveGroup(groupId: $groupId, userId: $userId) { + id + name + slug + myRoleInGroup + } } - } -` + ` +} -export const changeGroupMemberRoleMutation = gql` - mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { - ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) { - id - name - slug - myRoleInGroup +export const changeGroupMemberRoleMutation = () => { + return gql` + mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { + ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) { + id + name + slug + myRoleInGroup + } } - } -` + ` +} // ------ queries -export const groupQuery = gql` - query ($isMember: Boolean, $id: ID, $slug: String) { - Group(isMember: $isMember, id: $id, slug: $slug) { - id - name - slug - createdAt - updatedAt - disabled - deleted - about - description - groupType - actionRadius - categories { +export const groupQuery = () => { + return gql` + query ($isMember: Boolean, $id: ID, $slug: String) { + Group(isMember: $isMember, id: $id, slug: $slug) { id - slug name - icon + slug + createdAt + updatedAt + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + avatar { + url + } + locationName + location { + name + nameDE + nameEN + } + myRole } - avatar { - url - } - # locationName # test this as result - myRole } - } -` + ` +} -export const groupMembersQuery = gql` - query ($id: ID!) { - GroupMembers(id: $id) { - id - name - slug - myRoleInGroup +export const groupMembersQuery = () => { + return gql` + query ($id: ID!) { + GroupMembers(id: $id) { + id + name + slug + myRoleInGroup + } } - } -` + ` +} diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index 632aa8f4d..d693f9905 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -305,7 +305,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] authenticatedUser = await peterLustig.toJson() await Promise.all([ mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'g0', name: 'Investigative Journalism', @@ -313,27 +313,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] description: `

English:

This group is hidden.

What is our group for?

This group was created to allow investigative journalists to share and collaborate.

How does it work?

Here you can internally share posts and comments about them.


Deutsch:

Diese Gruppe ist verborgen.

Wofür ist unsere Gruppe?

Diese Gruppe wurde geschaffen, um investigativen Journalisten den Austausch und die Zusammenarbeit zu ermöglichen.

Wie funktioniert das?

Hier könnt ihr euch intern über Beiträge und Kommentare zu ihnen austauschen.

`, groupType: 'hidden', actionRadius: 'global', - categoryIds: ['cat6', 'cat9', 'cat14'], + categoryIds: ['cat6', 'cat12', 'cat16'], + locationName: 'Hamburg, Germany', }, }), ]) await Promise.all([ mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g0', userId: 'u2', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g0', userId: 'u4', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g0', userId: 'u6', @@ -342,7 +343,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g0', userId: 'u2', @@ -350,7 +351,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g0', userId: 'u4', @@ -362,7 +363,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] authenticatedUser = await jennyRostock.toJson() await Promise.all([ mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'g1', name: 'School For Citizens', @@ -370,41 +371,42 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] description: `

English

Our goal

Only those who enjoy learning and do not lose their curiosity can obtain a good education for life and continue to learn with joy throughout their lives.

Curiosity

For this we need a school that takes up the curiosity of the children, the people, and satisfies it through a lot of experience.


Deutsch

Unser Ziel

Nur wer Spaß am Lernen hat und seine Neugier nicht verliert, kann gute Bildung für's Leben erlangen und sein ganzes Leben mit Freude weiter lernen.

Neugier

Dazu benötigen wir eine Schule, die die Neugier der Kinder, der Menschen, aufnimmt und durch viel Erfahrung befriedigt.

`, groupType: 'closed', actionRadius: 'national', - categoryIds: ['cat7', 'cat9', 'cat16'], + categoryIds: ['cat8', 'cat14'], + locationName: 'France', }, }), ]) await Promise.all([ mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g1', userId: 'u1', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g1', userId: 'u2', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g1', userId: 'u5', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g1', userId: 'u6', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g1', userId: 'u7', @@ -413,7 +415,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g1', userId: 'u1', @@ -421,7 +423,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g1', userId: 'u5', @@ -429,7 +431,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g1', userId: 'u6', @@ -441,7 +443,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] authenticatedUser = await bobDerBaumeister.toJson() await Promise.all([ mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'g2', name: 'Yoga Practice', @@ -449,41 +451,41 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] description: `

What Is yoga?

Yoga is not just about practicing asanas. It's about how we do it.

And practicing asanas doesn't have to be yoga, it can be more athletic than yogic.

What makes practicing asanas yogic?

The important thing is:

`, groupType: 'public', actionRadius: 'interplanetary', - categoryIds: ['cat3', 'cat13', 'cat16'], + categoryIds: ['cat4', 'cat5', 'cat17'], }, }), ]) await Promise.all([ mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g2', userId: 'u3', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g2', userId: 'u4', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g2', userId: 'u5', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g2', userId: 'u6', }, }), mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'g2', userId: 'u7', @@ -492,31 +494,31 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g2', userId: 'u3', + roleInGroup: 'usual', + }, + }), + mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'g2', + userId: 'u4', roleInGroup: 'pending', }, }), mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'g2', - userId: 'u4', - roleInGroup: 'usual', - }, - }), - mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g2', userId: 'u5', - roleInGroup: 'usual', + roleInGroup: 'admin', }, }), mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'g2', userId: 'u6', diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index df2326db5..b73ba166f 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -82,7 +82,7 @@ const isAllowedToChangeGroupSettings = rule({ } }) -const isAllowedSeeingMembersOfGroup = rule({ +const isAllowedSeeingGroupMembers = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { if (!(user && user.id)) return false @@ -312,7 +312,7 @@ export default shield( statistics: allow, currentUser: allow, Group: isAuthenticated, - GroupMembers: isAllowedSeeingMembersOfGroup, + GroupMembers: isAllowedSeeingGroupMembers, Post: allow, profilePagePosts: allow, Comment: allow, diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index edb6b64eb..c600d0e52 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -51,6 +51,7 @@ beforeEach(async () => { await Factory.build('category', { id: 'cat9', name: 'Democracy & Politics', + slug: 'democracy-politics', icon: 'university', }) authenticatedUser = await admin.toJson() @@ -79,7 +80,7 @@ describe('slugifyMiddleware', () => { it('generates a slug based on name', async () => { await expect( mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables, }), ).resolves.toMatchObject({ @@ -93,13 +94,14 @@ describe('slugifyMiddleware', () => { actionRadius: 'national', }, }, + errors: undefined, }) }) it('generates a slug based on given slug', async () => { await expect( mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, slug: 'the-group', @@ -111,6 +113,7 @@ describe('slugifyMiddleware', () => { slug: 'the-group', }, }, + errors: undefined, }) }) }) @@ -118,7 +121,7 @@ describe('slugifyMiddleware', () => { describe('if slug exists', () => { beforeEach(async () => { await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, name: 'Pre-Existing Group', @@ -131,7 +134,7 @@ describe('slugifyMiddleware', () => { it('chooses another slug', async () => { await expect( mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, name: 'Pre-Existing Group', @@ -144,6 +147,7 @@ describe('slugifyMiddleware', () => { slug: 'pre-existing-group-1', }, }, + errors: undefined, }) }) @@ -152,7 +156,7 @@ describe('slugifyMiddleware', () => { try { await expect( mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, name: 'Pre-Existing Group', @@ -194,7 +198,7 @@ describe('slugifyMiddleware', () => { beforeEach(async () => { createGroupResult = await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { name: 'The Best Group', slug: 'the-best-group', @@ -213,7 +217,7 @@ describe('slugifyMiddleware', () => { it('has the new slug', async () => { await expect( mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: createGroupResult.data.CreateGroup.id, name: 'My Best Group', @@ -231,6 +235,7 @@ describe('slugifyMiddleware', () => { myRole: 'owner', }, }, + errors: undefined, }) }) }) @@ -239,7 +244,7 @@ describe('slugifyMiddleware', () => { it('has the new slug', async () => { await expect( mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: createGroupResult.data.CreateGroup.id, slug: 'my-best-group', @@ -257,6 +262,7 @@ describe('slugifyMiddleware', () => { myRole: 'owner', }, }, + errors: undefined, }) }) }) @@ -265,7 +271,7 @@ describe('slugifyMiddleware', () => { describe('if new slug exists in another group', () => { beforeEach(async () => { await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { name: 'Pre-Existing Group', slug: 'pre-existing-group', @@ -282,7 +288,7 @@ describe('slugifyMiddleware', () => { it('has unique slug "*-1"', async () => { await expect( mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: createGroupResult.data.CreateGroup.id, name: 'Pre-Existing Group', @@ -300,6 +306,7 @@ describe('slugifyMiddleware', () => { myRole: 'owner', }, }, + errors: undefined, }) }) }) @@ -309,7 +316,7 @@ describe('slugifyMiddleware', () => { try { await expect( mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: createGroupResult.data.CreateGroup.id, slug: 'pre-existing-group', @@ -368,6 +375,7 @@ describe('slugifyMiddleware', () => { slug: 'i-am-a-brand-new-post', }, }, + errors: undefined, }) }) @@ -386,6 +394,7 @@ describe('slugifyMiddleware', () => { slug: 'the-post', }, }, + errors: undefined, }) }) }) @@ -422,6 +431,7 @@ describe('slugifyMiddleware', () => { slug: 'pre-existing-post-1', }, }, + errors: undefined, }) }) @@ -504,6 +514,7 @@ describe('slugifyMiddleware', () => { slug: 'i-am-a-user', }, }, + errors: undefined, }) }) @@ -522,6 +533,7 @@ describe('slugifyMiddleware', () => { slug: 'the-user', }, }, + errors: undefined, }) }) }) @@ -546,6 +558,7 @@ describe('slugifyMiddleware', () => { slug: 'i-am-a-user-1', }, }, + errors: undefined, }) }) diff --git a/backend/src/schema/index.js b/backend/src/schema/index.js index 612487147..06e150c86 100644 --- a/backend/src/schema/index.js +++ b/backend/src/schema/index.js @@ -20,6 +20,7 @@ export default makeAugmentedSchema({ 'FILED', 'REVIEWED', 'Report', + 'Group', ], }, mutation: false, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 8d750284d..7a6298047 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -9,6 +9,7 @@ import Resolver, { convertObjectToCypherMapLiteral, } from './helpers/Resolver' import { mergeImage } from './images/images' +import { createOrUpdateLocations } from './users/location' export default { Query: { @@ -85,6 +86,7 @@ export default { CreateGroup: async (_parent, params, context, _resolveInfo) => { const { categoryIds } = params delete params.categoryIds + params.locationName = params.locationName === '' ? null : params.locationName if (CONFIG.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) { throw new UserInputError('Too view categories!') } @@ -135,7 +137,10 @@ export default { return group }) try { - return await writeTxResultPromise + const group = await writeTxResultPromise + // TODO: put in a middleware, see "UpdateGroup", "UpdateUser" + await createOrUpdateLocations('Group', params.id, params.locationName, session) + return group } catch (error) { if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') throw new UserInputError('Group with this slug already exists!') @@ -146,9 +151,11 @@ export default { }, UpdateGroup: async (_parent, params, context, _resolveInfo) => { const { categoryIds } = params - const { id: groupId, avatar: avatarInput } = params delete params.categoryIds + const { id: groupId, avatar: avatarInput } = params delete params.avatar + params.locationName = params.locationName === '' ? null : params.locationName + if (CONFIG.CATEGORIES_ACTIVE && categoryIds) { if (categoryIds.length < CATEGORIES_MIN) { throw new UserInputError('Too view categories!') @@ -206,7 +213,10 @@ export default { return group }) try { - return await writeTxResultPromise + const group = await writeTxResultPromise + // TODO: put in a middleware, see "CreateGroup", "UpdateUser" + await createOrUpdateLocations('Group', params.id, params.locationName, session) + return group } catch (error) { if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') throw new UserInputError('Group with this slug already exists!') @@ -300,12 +310,14 @@ export default { }, Group: { ...Resolver('Group', { + undefinedToNull: ['deleted', 'disabled', 'locationName', 'about'], hasMany: { categories: '-[:CATEGORIZED]->(related:Category)', posts: '<-[:IN]-(related:Post)', }, hasOne: { avatar: '-[:AVATAR_IMAGE]->(related:Image)', + location: '-[:IS_IN]->(related:Location)', }, }), }, diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index b4653edaf..d707440a4 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -55,18 +55,18 @@ const seedBasicsAndClearAuthentication = async () => { }, ) await Promise.all([ - neode.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - slug: 'democracy-politics', - icon: 'university', - }), neode.create('Category', { id: 'cat4', name: 'Environment & Nature', slug: 'environment-nature', icon: 'tree', }), + neode.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + slug: 'democracy-politics', + icon: 'university', + }), neode.create('Category', { id: 'cat15', name: 'Consumption & Sustainability', @@ -156,7 +156,7 @@ const seedComplexScenarioAndClearAuthentication = async () => { // public-group authenticatedUser = await usualMemberUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'public-group', name: 'The Best Group', @@ -168,14 +168,14 @@ const seedComplexScenarioAndClearAuthentication = async () => { }, }) await mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'public-group', userId: 'owner-of-closed-group', }, }) await mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'public-group', userId: 'owner-of-hidden-group', @@ -184,7 +184,7 @@ const seedComplexScenarioAndClearAuthentication = async () => { // closed-group authenticatedUser = await ownerMemberUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'closed-group', name: 'Uninteresting Group', @@ -198,7 +198,7 @@ const seedComplexScenarioAndClearAuthentication = async () => { // hidden-group authenticatedUser = await adminMemberUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'hidden-group', name: 'Investigative Journalism Group', @@ -211,7 +211,7 @@ const seedComplexScenarioAndClearAuthentication = async () => { }) // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'hidden-group', userId: 'admin-member-user', @@ -219,7 +219,7 @@ const seedComplexScenarioAndClearAuthentication = async () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'hidden-group', userId: 'second-owner-member-user', @@ -227,7 +227,7 @@ const seedComplexScenarioAndClearAuthentication = async () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'hidden-group', userId: 'admin-member-user', @@ -235,7 +235,7 @@ const seedComplexScenarioAndClearAuthentication = async () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'hidden-group', userId: 'second-owner-member-user', @@ -277,13 +277,13 @@ describe('in mode', () => { groupType: 'public', actionRadius: 'regional', categoryIds, - // locationName, // test this as result + locationName: 'Hamburg, Germany', } }) describe('unauthenticated', () => { it('throws authorization error', async () => { - const { errors } = await mutate({ mutation: createGroupMutation, variables }) + const { errors } = await mutate({ mutation: createGroupMutation(), variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -294,43 +294,50 @@ describe('in mode', () => { }) it('creates a group', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( - { - data: { - CreateGroup: { - name: 'The Best Group', - slug: 'the-group', - about: 'We will change the world!', - description: 'Some description' + descriptionAdditional100, - groupType: 'public', - actionRadius: 'regional', - }, + await expect( + mutate({ mutation: createGroupMutation(), variables }), + ).resolves.toMatchObject({ + data: { + CreateGroup: { + name: 'The Best Group', + slug: 'the-group', + about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + descriptionExcerpt: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', + locationName: 'Hamburg, Germany', + location: expect.objectContaining({ + name: 'Hamburg', + nameDE: 'Hamburg', + nameEN: 'Hamburg', + }), }, - errors: undefined, }, - ) + errors: undefined, + }) }) it('assigns the authenticated user as owner', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( - { - data: { - CreateGroup: { - name: 'The Best Group', - myRole: 'owner', - }, + await expect( + mutate({ mutation: createGroupMutation(), variables }), + ).resolves.toMatchObject({ + data: { + CreateGroup: { + name: 'The Best Group', + myRole: 'owner', }, - errors: undefined, }, - ) + errors: undefined, + }) }) it('has "disabled" and "deleted" default to "false"', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( - { - data: { CreateGroup: { disabled: false, deleted: false } }, - }, - ) + await expect( + mutate({ mutation: createGroupMutation(), variables }), + ).resolves.toMatchObject({ + data: { CreateGroup: { disabled: false, deleted: false } }, + }) }) describe('description', () => { @@ -338,7 +345,7 @@ describe('in mode', () => { describe('less then 100 chars', () => { it('throws error: "Description too short!"', async () => { const { errors } = await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, description: @@ -361,7 +368,7 @@ describe('in mode', () => { it('has new categories', async () => { await expect( mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, categoryIds: ['cat4', 'cat27'], @@ -386,7 +393,7 @@ describe('in mode', () => { describe('by "categoryIds: null"', () => { it('throws error: "Too view categories!"', async () => { const { errors } = await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, categoryIds: null }, }) expect(errors[0]).toHaveProperty('message', 'Too view categories!') @@ -396,7 +403,7 @@ describe('in mode', () => { describe('by "categoryIds: []"', () => { it('throws error: "Too view categories!"', async () => { const { errors } = await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, categoryIds: [] }, }) expect(errors[0]).toHaveProperty('message', 'Too view categories!') @@ -407,7 +414,7 @@ describe('in mode', () => { describe('four', () => { it('throws error: "Too many categories!"', async () => { const { errors } = await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] }, }) expect(errors[0]).toHaveProperty('message', 'Too many categories!') @@ -430,7 +437,7 @@ describe('in mode', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { - const { errors } = await query({ query: groupQuery, variables: {} }) + const { errors } = await query({ query: groupQuery(), variables: {} }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -464,7 +471,7 @@ describe('in mode', () => { ) authenticatedUser = await otherUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'others-group', name: 'Uninteresting Group', @@ -477,7 +484,7 @@ describe('in mode', () => { }) authenticatedUser = await ownerOfHiddenGroupUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'hidden-group', name: 'Investigative Journalism Group', @@ -489,7 +496,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'second-hidden-group', name: 'Second Investigative Journalism Group', @@ -501,7 +508,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'second-hidden-group', userId: 'current-user', @@ -509,7 +516,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'third-hidden-group', name: 'Third Investigative Journalism Group', @@ -521,7 +528,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'third-hidden-group', userId: 'current-user', @@ -530,7 +537,7 @@ describe('in mode', () => { }) authenticatedUser = await user.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'my-group', name: 'The Best Group', @@ -539,6 +546,7 @@ describe('in mode', () => { groupType: 'public', actionRadius: 'regional', categoryIds, + locationName: 'Hamburg, Germany', }, }) }) @@ -546,25 +554,38 @@ describe('in mode', () => { describe('query groups', () => { describe('in general finds only listed groups – no hidden groups where user is none or pending member', () => { describe('without any filters', () => { - it('finds all listed groups', async () => { - const result = await query({ query: groupQuery, variables: {} }) + it('finds all listed groups – including the set descriptionExcerpts and locations', async () => { + const result = await query({ query: groupQuery(), variables: {} }) expect(result).toMatchObject({ data: { Group: expect.arrayContaining([ expect.objectContaining({ id: 'my-group', slug: 'the-best-group', + descriptionExcerpt: 'Some description' + descriptionAdditional100, + locationName: 'Hamburg, Germany', + location: expect.objectContaining({ + name: 'Hamburg', + nameDE: 'Hamburg', + nameEN: 'Hamburg', + }), myRole: 'owner', }), expect.objectContaining({ id: 'others-group', slug: 'uninteresting-group', + descriptionExcerpt: 'We love it like it is!?' + descriptionAdditional100, + locationName: null, + location: null, myRole: null, }), expect.objectContaining({ id: 'third-hidden-group', slug: 'third-investigative-journalism-group', + descriptionExcerpt: 'We research …' + descriptionAdditional100, myRole: 'usual', + locationName: null, + location: null, }), ]), }, @@ -579,7 +600,9 @@ describe('in mode', () => { }) it('has set categories', async () => { - await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({ + await expect( + query({ query: groupQuery(), variables: {} }), + ).resolves.toMatchObject({ data: { Group: expect.arrayContaining([ expect.objectContaining({ @@ -613,7 +636,7 @@ describe('in mode', () => { describe('with given id', () => { describe("id = 'my-group'", () => { it('finds only the listed group with this id', async () => { - const result = await query({ query: groupQuery, variables: { id: 'my-group' } }) + const result = await query({ query: groupQuery(), variables: { id: 'my-group' } }) expect(result).toMatchObject({ data: { Group: [ @@ -633,7 +656,7 @@ describe('in mode', () => { describe("id = 'third-hidden-group'", () => { it("finds only the hidden group where I'm 'usual' member", async () => { const result = await query({ - query: groupQuery, + query: groupQuery(), variables: { id: 'third-hidden-group' }, }) expect(result).toMatchObject({ @@ -655,7 +678,7 @@ describe('in mode', () => { describe("id = 'second-hidden-group'", () => { it("finds no hidden group where I'm 'pending' member", async () => { const result = await query({ - query: groupQuery, + query: groupQuery(), variables: { id: 'second-hidden-group' }, }) expect(result.data.Group.length).toBe(0) @@ -665,7 +688,7 @@ describe('in mode', () => { describe("id = 'hidden-group'", () => { it("finds no hidden group where I'm not(!) a member at all", async () => { const result = await query({ - query: groupQuery, + query: groupQuery(), variables: { id: 'hidden-group' }, }) expect(result.data.Group.length).toBe(0) @@ -677,7 +700,7 @@ describe('in mode', () => { describe("slug = 'the-best-group'", () => { it('finds only the listed group with this slug', async () => { const result = await query({ - query: groupQuery, + query: groupQuery(), variables: { slug: 'the-best-group' }, }) expect(result).toMatchObject({ @@ -699,7 +722,7 @@ describe('in mode', () => { describe("slug = 'third-investigative-journalism-group'", () => { it("finds only the hidden group where I'm 'usual' member", async () => { const result = await query({ - query: groupQuery, + query: groupQuery(), variables: { slug: 'third-investigative-journalism-group' }, }) expect(result).toMatchObject({ @@ -721,7 +744,7 @@ describe('in mode', () => { describe("slug = 'second-investigative-journalism-group'", () => { it("finds no hidden group where I'm 'pending' member", async () => { const result = await query({ - query: groupQuery, + query: groupQuery(), variables: { slug: 'second-investigative-journalism-group' }, }) expect(result.data.Group.length).toBe(0) @@ -731,7 +754,7 @@ describe('in mode', () => { describe("slug = 'investigative-journalism-group'", () => { it("finds no hidden group where I'm not(!) a member at all", async () => { const result = await query({ - query: groupQuery, + query: groupQuery(), variables: { slug: 'investigative-journalism-group' }, }) expect(result.data.Group.length).toBe(0) @@ -741,7 +764,7 @@ describe('in mode', () => { describe('isMember = true', () => { it('finds only listed groups where user is member', async () => { - const result = await query({ query: groupQuery, variables: { isMember: true } }) + const result = await query({ query: groupQuery(), variables: { isMember: true } }) expect(result).toMatchObject({ data: { Group: expect.arrayContaining([ @@ -765,7 +788,7 @@ describe('in mode', () => { describe('isMember = false', () => { it('finds only listed groups where user is not(!) member', async () => { - const result = await query({ query: groupQuery, variables: { isMember: false } }) + const result = await query({ query: groupQuery(), variables: { isMember: false } }) expect(result).toMatchObject({ data: { Group: expect.arrayContaining([ @@ -798,7 +821,7 @@ describe('in mode', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'not-existing-group', userId: 'current-user', @@ -840,7 +863,7 @@ describe('in mode', () => { // public-group authenticatedUser = await ownerOfClosedGroupUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'closed-group', name: 'Uninteresting Group', @@ -853,7 +876,7 @@ describe('in mode', () => { }) authenticatedUser = await ownerOfHiddenGroupUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'hidden-group', name: 'Investigative Journalism Group', @@ -866,7 +889,7 @@ describe('in mode', () => { }) authenticatedUser = await user.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'public-group', name: 'The Best Group', @@ -884,7 +907,7 @@ describe('in mode', () => { it('has "usual" as membership role', async () => { await expect( mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'public-group', userId: 'owner-of-closed-group', @@ -907,7 +930,7 @@ describe('in mode', () => { it('has still "owner" as membership role', async () => { await expect( mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'public-group', userId: 'current-user', @@ -932,7 +955,7 @@ describe('in mode', () => { it('has "pending" as membership role', async () => { await expect( mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'closed-group', userId: 'current-user', @@ -955,7 +978,7 @@ describe('in mode', () => { it('has still "owner" as membership role', async () => { await expect( mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'closed-group', userId: 'owner-of-closed-group', @@ -979,7 +1002,7 @@ describe('in mode', () => { describe('joined by "owner-of-closed-group"', () => { it('throws authorization error', async () => { const { errors } = await query({ - query: joinGroupMutation, + query: joinGroupMutation(), variables: { groupId: 'hidden-group', userId: 'owner-of-closed-group', @@ -994,7 +1017,7 @@ describe('in mode', () => { it('has still "owner" as membership role', async () => { await expect( mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'hidden-group', userId: 'owner-of-hidden-group', @@ -1030,7 +1053,7 @@ describe('in mode', () => { variables = { id: 'not-existing-group', } - const { errors } = await query({ query: groupMembersQuery, variables }) + const { errors } = await query({ query: groupMembersQuery(), variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1091,7 +1114,7 @@ describe('in mode', () => { // public-group authenticatedUser = await user.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'public-group', name: 'The Best Group', @@ -1103,14 +1126,14 @@ describe('in mode', () => { }, }) await mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'public-group', userId: 'owner-of-closed-group', }, }) await mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'public-group', userId: 'owner-of-hidden-group', @@ -1119,7 +1142,7 @@ describe('in mode', () => { // closed-group authenticatedUser = await ownerOfClosedGroupUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'closed-group', name: 'Uninteresting Group', @@ -1131,14 +1154,14 @@ describe('in mode', () => { }, }) await mutate({ - mutation: joinGroupMutation, + mutation: joinGroupMutation(), variables: { groupId: 'closed-group', userId: 'current-user', }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'closed-group', userId: 'owner-of-hidden-group', @@ -1148,7 +1171,7 @@ describe('in mode', () => { // hidden-group authenticatedUser = await ownerOfHiddenGroupUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'hidden-group', name: 'Investigative Journalism Group', @@ -1161,7 +1184,7 @@ describe('in mode', () => { }) // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'hidden-group', userId: 'pending-user', @@ -1169,7 +1192,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'hidden-group', userId: 'current-user', @@ -1177,7 +1200,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'hidden-group', userId: 'owner-of-closed-group', @@ -1203,7 +1226,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1236,7 +1259,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1269,7 +1292,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1312,7 +1335,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1345,7 +1368,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1377,7 +1400,7 @@ describe('in mode', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMembersQuery, variables }) + const { errors } = await query({ query: groupMembersQuery(), variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1388,7 +1411,7 @@ describe('in mode', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMembersQuery, variables }) + const { errors } = await query({ query: groupMembersQuery(), variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1410,7 +1433,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1447,7 +1470,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1484,7 +1507,7 @@ describe('in mode', () => { it('finds all members', async () => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables, }) expect(result).toMatchObject({ @@ -1520,7 +1543,7 @@ describe('in mode', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMembersQuery, variables }) + const { errors } = await query({ query: groupMembersQuery(), variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1531,7 +1554,7 @@ describe('in mode', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMembersQuery, variables }) + const { errors } = await query({ query: groupMembersQuery(), variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1552,7 +1575,7 @@ describe('in mode', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'not-existing-group', userId: 'current-user', @@ -1596,7 +1619,7 @@ describe('in mode', () => { it('has role usual', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -1634,7 +1657,7 @@ describe('in mode', () => { it('has role admin', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -1669,7 +1692,7 @@ describe('in mode', () => { it('has role owner', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -1711,7 +1734,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1737,7 +1760,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1755,7 +1778,7 @@ describe('in mode', () => { it('has role owner still', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -1786,7 +1809,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1809,7 +1832,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1832,7 +1855,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1865,7 +1888,7 @@ describe('in mode', () => { it('has role owner', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -1890,7 +1913,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1913,7 +1936,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1930,7 +1953,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1953,7 +1976,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1970,7 +1993,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -1993,7 +2016,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2010,7 +2033,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2043,7 +2066,7 @@ describe('in mode', () => { it('has role admin', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -2069,7 +2092,7 @@ describe('in mode', () => { it('has role usual again', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -2100,7 +2123,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2117,7 +2140,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2140,7 +2163,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2157,7 +2180,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2180,7 +2203,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2197,7 +2220,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2230,7 +2253,7 @@ describe('in mode', () => { it('has role usual', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -2256,7 +2279,7 @@ describe('in mode', () => { it('has role usual again', async () => { await expect( mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }), ).resolves.toMatchObject({ @@ -2287,7 +2310,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2310,7 +2333,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2333,7 +2356,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') @@ -2352,7 +2375,7 @@ describe('in mode', () => { // closed-group authenticatedUser = await ownerMemberUser.toJson() await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'closed-group', userId: 'pending-member-user', @@ -2360,7 +2383,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'closed-group', userId: 'usual-member-user', @@ -2368,7 +2391,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'closed-group', userId: 'admin-member-user', @@ -2376,7 +2399,7 @@ describe('in mode', () => { }, }) await mutate({ - mutation: changeGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation(), variables: { groupId: 'closed-group', userId: 'second-owner-member-user', @@ -2394,7 +2417,7 @@ describe('in mode', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { groupId: 'not-existing-group', userId: 'current-user', @@ -2409,7 +2432,7 @@ describe('in mode', () => { describe('here "closed-group" for example', () => { const memberInGroup = async (userId, groupId) => { const result = await query({ - query: groupMembersQuery, + query: groupMembersQuery(), variables: { id: groupId, }, @@ -2433,7 +2456,7 @@ describe('in mode', () => { authenticatedUser = await pendingMemberUser.toJson() await expect( mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'pending-member-user', @@ -2460,7 +2483,7 @@ describe('in mode', () => { authenticatedUser = await usualMemberUser.toJson() await expect( mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'usual-member-user', @@ -2487,7 +2510,7 @@ describe('in mode', () => { authenticatedUser = await adminMemberUser.toJson() await expect( mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'admin-member-user', @@ -2511,7 +2534,7 @@ describe('in mode', () => { it('throws authorization error', async () => { authenticatedUser = await ownerMemberUser.toJson() const { errors } = await mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'owner-member-user', @@ -2525,7 +2548,7 @@ describe('in mode', () => { it('throws authorization error', async () => { authenticatedUser = await secondOwnerMemberUser.toJson() const { errors } = await mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'second-owner-member-user', @@ -2539,7 +2562,7 @@ describe('in mode', () => { it('throws authorization error', async () => { authenticatedUser = await noMemberUser.toJson() const { errors } = await mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'none-member-user', @@ -2553,7 +2576,7 @@ describe('in mode', () => { it('throws authorization error', async () => { authenticatedUser = await ownerMemberUser.toJson() const { errors } = await mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'usual-member-user', @@ -2567,7 +2590,7 @@ describe('in mode', () => { it('throws authorization error', async () => { authenticatedUser = await usualMemberUser.toJson() const { errors } = await mutate({ - mutation: leaveGroupMutation, + mutation: leaveGroupMutation(), variables: { ...variables, userId: 'admin-member-user', @@ -2593,7 +2616,7 @@ describe('in mode', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - query: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: 'my-group', slug: 'my-best-group', @@ -2604,23 +2627,34 @@ describe('in mode', () => { }) describe('authenticated', () => { - let otherUser + let noMemberUser beforeAll(async () => { - otherUser = await Factory.build( + noMemberUser = await Factory.build( 'user', { - id: 'other-user', - name: 'Other TestUser', + id: 'none-member-user', + name: 'None Member TestUser', }, { - email: 'test2@example.org', + email: 'none-member-user@example.org', password: '1234', }, ) - authenticatedUser = await otherUser.toJson() + usualMemberUser = await Factory.build( + 'user', + { + id: 'usual-member-user', + name: 'Usual Member TestUser', + }, + { + email: 'usual-member-user@example.org', + password: '1234', + }, + ) + authenticatedUser = await noMemberUser.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'others-group', name: 'Uninteresting Group', @@ -2633,7 +2667,7 @@ describe('in mode', () => { }) authenticatedUser = await user.toJson() await mutate({ - mutation: createGroupMutation, + mutation: createGroupMutation(), variables: { id: 'my-group', name: 'The Best Group', @@ -2642,6 +2676,15 @@ describe('in mode', () => { groupType: 'public', actionRadius: 'regional', categoryIds, + locationName: 'Berlin, Germany', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'my-group', + userId: 'usual-member-user', + roleInGroup: 'usual', }, }) }) @@ -2652,35 +2695,170 @@ describe('in mode', () => { authenticatedUser = await user.toJson() }) - it('has set the settings', async () => { - await expect( - mutate({ - mutation: updateGroupMutation, - variables: { - id: 'my-group', - name: 'The New Group For Our Country', - about: 'We will change the land!', - description: 'Some country relevant description' + descriptionAdditional100, - actionRadius: 'national', - // avatar, // test this as result - // locationName, // test this as result + describe('all standard settings – excluding location', () => { + it('has updated the settings', async () => { + await expect( + mutate({ + mutation: updateGroupMutation(), + variables: { + id: 'my-group', + name: 'The New Group For Our Country', + about: 'We will change the land!', + description: 'Some country relevant description' + descriptionAdditional100, + actionRadius: 'national', + // avatar, // test this as result + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + id: 'my-group', + name: 'The New Group For Our Country', + slug: 'the-new-group-for-our-country', // changing the slug is tested in the slugifyMiddleware + about: 'We will change the land!', + description: 'Some country relevant description' + descriptionAdditional100, + descriptionExcerpt: + 'Some country relevant description' + descriptionAdditional100, + actionRadius: 'national', + // avatar, // test this as result + myRole: 'owner', + }, }, - }), - ).resolves.toMatchObject({ - data: { - UpdateGroup: { - id: 'my-group', - name: 'The New Group For Our Country', - slug: 'the-new-group-for-our-country', // changing the slug is tested in the slugifyMiddleware - about: 'We will change the land!', - description: 'Some country relevant description' + descriptionAdditional100, - actionRadius: 'national', - // avatar, // test this as result - // locationName, // test this as result - myRole: 'owner', - }, - }, - errors: undefined, + errors: undefined, + }) + }) + }) + + describe('location', () => { + describe('"locationName" is undefined – shall not change location', () => { + it('has left locaton unchanged as "Berlin"', async () => { + await expect( + mutate({ + mutation: updateGroupMutation(), + variables: { + id: 'my-group', + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + id: 'my-group', + locationName: 'Berlin, Germany', + location: expect.objectContaining({ + name: 'Berlin', + nameDE: 'Berlin', + nameEN: 'Berlin', + }), + myRole: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + + describe('"locationName" is null – shall change location "Berlin" to unset location', () => { + it('has updated the location to unset location', async () => { + await expect( + mutate({ + mutation: updateGroupMutation(), + variables: { + id: 'my-group', + locationName: null, + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + id: 'my-group', + locationName: null, + location: null, + myRole: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + + describe('change unset location to "Paris"', () => { + it('has updated the location to "Paris"', async () => { + await expect( + mutate({ + mutation: updateGroupMutation(), + variables: { + id: 'my-group', + locationName: 'Paris, France', + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + id: 'my-group', + locationName: 'Paris, France', + location: expect.objectContaining({ + name: 'Paris', + nameDE: 'Paris', + nameEN: 'Paris', + }), + myRole: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + + describe('change location "Paris" to "Hamburg"', () => { + it('has updated the location to "Hamburg"', async () => { + await expect( + mutate({ + mutation: updateGroupMutation(), + variables: { + id: 'my-group', + locationName: 'Hamburg, Germany', + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + id: 'my-group', + locationName: 'Hamburg, Germany', + location: expect.objectContaining({ + name: 'Hamburg', + nameDE: 'Hamburg', + nameEN: 'Hamburg', + }), + myRole: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + + describe('"locationName" is empty string – shall change location "Hamburg" to unset location ', () => { + it('has updated the location to unset', async () => { + await expect( + mutate({ + mutation: updateGroupMutation(), + variables: { + id: 'my-group', + locationName: '', // empty string '' sets it to null + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + id: 'my-group', + locationName: null, + location: null, + myRole: 'owner', + }, + }, + errors: undefined, + }) + }) }) }) @@ -2689,7 +2867,7 @@ describe('in mode', () => { describe('less then 100 chars', () => { it('throws error: "Description too short!"', async () => { const { errors } = await mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: 'my-group', description: @@ -2712,7 +2890,7 @@ describe('in mode', () => { it('has new categories', async () => { await expect( mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: 'my-group', categoryIds: ['cat4', 'cat27'], @@ -2738,7 +2916,7 @@ describe('in mode', () => { describe('by "categoryIds: []"', () => { it('throws error: "Too view categories!"', async () => { const { errors } = await mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: 'my-group', categoryIds: [], @@ -2752,7 +2930,7 @@ describe('in mode', () => { describe('four', () => { it('throws error: "Too many categories!"', async () => { const { errors } = await mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: 'my-group', categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'], @@ -2764,20 +2942,36 @@ describe('in mode', () => { }) }) - describe('as no(!) owner', () => { + describe('as "usual-member-user" member, no(!) owner', () => { it('throws authorization error', async () => { - authenticatedUser = await otherUser.toJson() + authenticatedUser = await usualMemberUser.toJson() const { errors } = await mutate({ - mutation: updateGroupMutation, + mutation: updateGroupMutation(), variables: { id: 'my-group', name: 'The New Group For Our Country', about: 'We will change the land!', description: 'Some country relevant description' + descriptionAdditional100, actionRadius: 'national', - categoryIds: ['cat4', 'cat27'], // test this as result - // avatar, // test this as result - // locationName, // test this as result + categoryIds: ['cat4', 'cat27'], + }, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') + }) + }) + + describe('as "none-member-user"', () => { + it('throws authorization error', async () => { + authenticatedUser = await noMemberUser.toJson() + const { errors } = await mutate({ + mutation: updateGroupMutation(), + variables: { + id: 'my-group', + name: 'The New Group For Our Country', + about: 'We will change the land!', + description: 'Some country relevant description' + descriptionAdditional100, + actionRadius: 'national', + categoryIds: ['cat4', 'cat27'], }, }) expect(errors[0]).toHaveProperty('message', 'Not Authorized!') diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 12f00ffb6..1ce3b986f 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -4,7 +4,7 @@ import { UserInputError, ForbiddenError } from 'apollo-server' import { mergeImage, deleteImage } from './images/images' import Resolver from './helpers/Resolver' import log from './helpers/databaseLogger' -import createOrUpdateLocations from './users/location' +import { createOrUpdateLocations } from './users/location' const neode = getNeode() @@ -139,9 +139,10 @@ export default { return blockedUser.toJson() }, UpdateUser: async (_parent, params, context, _resolveInfo) => { - const { termsAndConditionsAgreedVersion } = params const { avatar: avatarInput } = params delete params.avatar + params.locationName = params.locationName === '' ? null : params.locationName + const { termsAndConditionsAgreedVersion } = params if (termsAndConditionsAgreedVersion) { const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g) if (!regEx.test(termsAndConditionsAgreedVersion)) { @@ -169,7 +170,8 @@ export default { }) try { const user = await writeTxResultPromise - await createOrUpdateLocations(params.id, params.locationName, session) + // TODO: put in a middleware, see "CreateGroup", "UpdateGroup" + await createOrUpdateLocations('User', params.id, params.locationName, session) return user } catch (error) { throw new UserInputError(error.message) diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index d8fce3b29..87226ec4d 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -161,7 +161,7 @@ describe('UpdateUser', () => { $id: ID! $name: String $termsAndConditionsAgreedVersion: String - $locationName: String + $locationName: String # empty string '' sets it to null ) { UpdateUser( id: $id @@ -174,6 +174,11 @@ describe('UpdateUser', () => { termsAndConditionsAgreedVersion termsAndConditionsAgreedAt locationName + location { + name + nameDE + nameEN + } } } ` @@ -289,11 +294,39 @@ describe('UpdateUser', () => { expect(errors[0]).toHaveProperty('message', 'Invalid version format!') }) - it('supports updating location', async () => { - variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' } - await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ - data: { UpdateUser: { locationName: 'Hamburg, New Jersey, United States' } }, - errors: undefined, + describe('supports updating location', () => { + describe('change location to "Hamburg, New Jersey, United States"', () => { + it('has updated location to "Hamburg, New Jersey, United States"', async () => { + variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' } + await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ + data: { + UpdateUser: { + locationName: 'Hamburg, New Jersey, United States', + location: expect.objectContaining({ + name: 'Hamburg', + nameDE: 'Hamburg', + nameEN: 'Hamburg', + }), + }, + }, + errors: undefined, + }) + }) + }) + + describe('change location to unset location', () => { + it('has updated location to unset location', async () => { + variables = { ...variables, locationName: '' } + await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ + data: { + UpdateUser: { + locationName: null, + location: null, + }, + }, + errors: undefined, + }) + }) }) }) }) diff --git a/backend/src/schema/resolvers/users/location.js b/backend/src/schema/resolvers/users/location.js index affd3267e..9d8a11f89 100644 --- a/backend/src/schema/resolvers/users/location.js +++ b/backend/src/schema/resolvers/users/location.js @@ -1,6 +1,5 @@ import request from 'request' import { UserInputError } from 'apollo-server' -import isEmpty from 'lodash/isEmpty' import Debug from 'debug' import asyncForEach from '../../../helpers/asyncForEach' import CONFIG from '../../../config' @@ -62,77 +61,86 @@ const createLocation = async (session, mapboxData) => { }) } -const createOrUpdateLocations = async (userId, locationName, session) => { - if (isEmpty(locationName)) { - return - } - const res = await fetch( - `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent( - locationName, - )}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${locales.join( - ',', - )}`, - ) +export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, session) => { + if (locationName === undefined) return - debug(res) + let locationId - if (!res || !res.features || !res.features[0]) { - throw new UserInputError('locationName is invalid') - } + if (locationName !== null) { + const res = await fetch( + `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent( + locationName, + )}.json?access_token=${ + CONFIG.MAPBOX_TOKEN + }&types=region,place,country&language=${locales.join(',')}`, + ) - let data + debug(res) - res.features.forEach((item) => { - if (item.matching_place_name === locationName) { - data = item + if (!res || !res.features || !res.features[0]) { + throw new UserInputError('locationName is invalid') } - }) - if (!data) { - data = res.features[0] - } - if (!data || !data.place_type || !data.place_type.length) { - throw new UserInputError('locationName is invalid') - } + let data - if (data.place_type.length > 1) { - data.id = 'region.' + data.id.split('.')[1] - } - await createLocation(session, data) - - let parent = data - - if (data.context) { - await asyncForEach(data.context, async (ctx) => { - await createLocation(session, ctx) - await session.writeTransaction((transaction) => { - return transaction.run( - ` - MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) - MERGE (child)<-[:IS_IN]-(parent) - RETURN child.id, parent.id - `, - { - parentId: parent.id, - childId: ctx.id, - }, - ) - }) - parent = ctx + res.features.forEach((item) => { + if (item.matching_place_name === locationName) { + data = item + } }) + if (!data) { + data = res.features[0] + } + + if (!data || !data.place_type || !data.place_type.length) { + throw new UserInputError('locationName is invalid') + } + + if (data.place_type.length > 1) { + data.id = 'region.' + data.id.split('.')[1] + } + await createLocation(session, data) + + let parent = data + + if (data.context) { + await asyncForEach(data.context, async (ctx) => { + await createLocation(session, ctx) + await session.writeTransaction((transaction) => { + return transaction.run( + ` + MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) + MERGE (child)<-[:IS_IN]-(parent) + RETURN child.id, parent.id + `, + { + parentId: parent.id, + childId: ctx.id, + }, + ) + }) + parent = ctx + }) + } + + locationId = data.id + } else { + locationId = 'non-existent-id' } - // delete all current locations from user and add new location + + // delete all current locations from node and add new location await session.writeTransaction((transaction) => { return transaction.run( ` - MATCH (user:User {id: $userId})-[relationship:IS_IN]->(location:Location) - DETACH DELETE relationship - WITH user - MATCH (location:Location {id: $locationId}) - MERGE (user)-[:IS_IN]->(location) - RETURN location.id, user.id - `, - { userId: userId, locationId: data.id }, + MATCH (node:${nodeLabel} {id: $nodeId}) + OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location) + DELETE relationship + WITH node + MATCH (location:Location {id: $locationId}) + MERGE (node)-[:IS_IN]->(location) + RETURN location.id, node.id + `, + { nodeId, locationId }, ) }) } @@ -147,5 +155,3 @@ export const queryLocations = async ({ place, lang }) => { } return res.features } - -export default createOrUpdateLocations diff --git a/backend/src/schema/resolvers/users/location.spec.js b/backend/src/schema/resolvers/users/location.spec.js index 92f08893e..c0525ca1c 100644 --- a/backend/src/schema/resolvers/users/location.spec.js +++ b/backend/src/schema/resolvers/users/location.spec.js @@ -121,7 +121,7 @@ describe('Location Service', () => { const result = await query({ query: queryLocations, variables }) expect(result.data.queryLocations).toEqual( expect.arrayContaining([ - { id: 'place.14094307404564380', place_name: 'Berlin, Germany' }, + { id: expect.stringMatching(/^place\.[0-9]+$/), place_name: 'Berlin, Germany' }, { id: expect.stringMatching(/^place\.[0-9]+$/), place_name: 'Berlin, Maryland, United States', diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index bab19307e..15b980345 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -29,11 +29,12 @@ type Group { about: String # goal description: String! + descriptionExcerpt: String! groupType: GroupType! actionRadius: GroupActionRadius! - location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") locationName: String + location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT") @@ -104,7 +105,7 @@ type Mutation { actionRadius: GroupActionRadius! categoryIds: [ID] # avatar: ImageInput # a group can not be created with an avatar - locationName: String # test this as result + locationName: String # empty string '' sets it to null ): Group UpdateGroup( @@ -117,7 +118,7 @@ type Mutation { actionRadius: GroupActionRadius categoryIds: [ID] avatar: ImageInput # test this as result - locationName: String # test this as result + locationName: String # empty string '' sets it to null ): Group # DeleteGroup(id: ID!): Group diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 2861b0fda..fdab73d17 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -33,8 +33,8 @@ type User { invitedBy: User @relation(name: "INVITED", direction: "IN") invited: [User] @relation(name: "INVITED", direction: "OUT") - location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") locationName: String + location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") about: String socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN") @@ -212,7 +212,7 @@ type Mutation { email: String slug: String avatar: ImageInput - locationName: String + locationName: String # empty string '' sets it to null about: String termsAndConditionsAgreedVersion: String termsAndConditionsAgreedAt: String diff --git a/package.json b/package.json index d7463adac..54560aebd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social", - "version": "1.1.0", + "version": "1.1.1", "description": "Free and open source software program code available to run social networks.", "author": "ocelot.social Community", "license": "MIT", diff --git a/webapp/assets/_new/styles/tokens.scss b/webapp/assets/_new/styles/tokens.scss index 53abd01f8..180f9c820 100644 --- a/webapp/assets/_new/styles/tokens.scss +++ b/webapp/assets/_new/styles/tokens.scss @@ -268,6 +268,7 @@ $size-avatar-large: 114px; * @presenter Spacing */ + $size-button-large: 50px; $size-button-base: 36px; $size-button-small: 26px; @@ -356,3 +357,22 @@ $media-query-x-large: (min-width: 1200px); /** * @tokens Background Images */ + +/** + * @tokens Header Color + */ + +$color-header-background: $color-neutral-100; + +/** + * @tokens Footer Color + */ + +$color-footer-background: $color-neutral-100; +$color-footer-link: $color-primary; + +/** + * @tokens Locale Menu Color + */ + +$color-locale-menu: $text-color-soft; \ No newline at end of file diff --git a/webapp/assets/styles/imports/_branding.scss b/webapp/assets/styles/imports/_branding.scss new file mode 100644 index 000000000..75058595d --- /dev/null +++ b/webapp/assets/styles/imports/_branding.scss @@ -0,0 +1,5 @@ +/* + * + * Here, all SCSS variables and classes can be adapted to your custom design. + * +*/ \ No newline at end of file diff --git a/webapp/assets/styles/main.scss b/webapp/assets/styles/main.scss index d6821e013..144701f83 100644 --- a/webapp/assets/styles/main.scss +++ b/webapp/assets/styles/main.scss @@ -179,4 +179,4 @@ hr { .dropdown-arrow { font-size: $font-size-xx-small; -} +} \ No newline at end of file diff --git a/webapp/components/AvatarMenu/AvatarMenu.spec.js b/webapp/components/AvatarMenu/AvatarMenu.spec.js index 77de68de4..15f536ee7 100644 --- a/webapp/components/AvatarMenu/AvatarMenu.spec.js +++ b/webapp/components/AvatarMenu/AvatarMenu.spec.js @@ -90,6 +90,13 @@ describe('AvatarMenu.vue', () => { expect(profileLink.exists()).toBe(true) }) + it('displays a link to "My groups"', () => { + const profileLink = wrapper + .findAll('.ds-menu-item span') + .at(wrapper.vm.routes.findIndex((route) => route.path === '/my-groups')) + expect(profileLink.exists()).toBe(true) + }) + it('displays a link to the notifications page', () => { const notificationsLink = wrapper .findAll('.ds-menu-item span') @@ -103,6 +110,11 @@ describe('AvatarMenu.vue', () => { .at(wrapper.vm.routes.findIndex((route) => route.path === '/settings')) expect(settingsLink.exists()).toBe(true) }) + + it('displays a total of 4 links', () => { + const allLinks = wrapper.findAll('.ds-menu-item') + expect(allLinks).toHaveLength(4) + }) }) describe('role moderator', () => { @@ -125,9 +137,9 @@ describe('AvatarMenu.vue', () => { expect(moderationLink.exists()).toBe(true) }) - it('displays a total of 4 links', () => { + it('displays a total of 5 links', () => { const allLinks = wrapper.findAll('.ds-menu-item') - expect(allLinks).toHaveLength(4) + expect(allLinks).toHaveLength(5) }) }) @@ -151,9 +163,9 @@ describe('AvatarMenu.vue', () => { expect(adminLink.exists()).toBe(true) }) - it('displays a total of 5 links', () => { + it('displays a total of 6 links', () => { const allLinks = wrapper.findAll('.ds-menu-item') - expect(allLinks).toHaveLength(5) + expect(allLinks).toHaveLength(6) }) }) }) diff --git a/webapp/components/AvatarMenu/AvatarMenu.vue b/webapp/components/AvatarMenu/AvatarMenu.vue index 061e96221..5caec07f2 100644 --- a/webapp/components/AvatarMenu/AvatarMenu.vue +++ b/webapp/components/AvatarMenu/AvatarMenu.vue @@ -72,10 +72,15 @@ export default { } const routes = [ { - name: this.$t('profile.name'), + name: this.$t('header.avatarMenu.myProfile'), path: `/profile/${this.user.id}/${this.user.slug}`, icon: 'user', }, + { + name: this.$t('header.avatarMenu.myGroups'), + path: '/my-groups', + icon: 'users', + }, { name: this.$t('notifications.pageLink'), path: '/notifications', diff --git a/webapp/components/Button/JoinLeaveButton.vue b/webapp/components/Button/JoinLeaveButton.vue index 993631065..9bcb5042a 100644 --- a/webapp/components/Button/JoinLeaveButton.vue +++ b/webapp/components/Button/JoinLeaveButton.vue @@ -1,6 +1,6 @@