From a8a6acb9f7944121e069f2a15462d1fe5f8b8281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 3 Aug 2022 08:13:53 +0200 Subject: [PATCH 01/10] Rename 'UserGroup' to 'UserRole' --- backend/src/schema/resolvers/users.spec.js | 2 +- .../schema/types/enum/{UserGroup.gql => UserRole.gql} | 2 +- backend/src/schema/types/type/User.gql | 10 +++++----- webapp/graphql/admin/Roles.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename backend/src/schema/types/enum/{UserGroup.gql => UserRole.gql} (63%) diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 102d2f6fe..9a88dc945 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -45,7 +45,7 @@ const deleteUserMutation = gql` } ` const switchUserRoleMutation = gql` - mutation ($role: UserGroup!, $id: ID!) { + mutation ($role: UserRole!, $id: ID!) { switchUserRole(role: $role, id: $id) { name role diff --git a/backend/src/schema/types/enum/UserGroup.gql b/backend/src/schema/types/enum/UserRole.gql similarity index 63% rename from backend/src/schema/types/enum/UserGroup.gql rename to backend/src/schema/types/enum/UserRole.gql index 587e90c71..846afde13 100644 --- a/backend/src/schema/types/enum/UserGroup.gql +++ b/backend/src/schema/types/enum/UserRole.gql @@ -1,4 +1,4 @@ -enum UserGroup { +enum UserRole { admin moderator user diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 772dedf6b..871e73ad8 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -28,7 +28,7 @@ type User { avatar: Image @relation(name: "AVATAR_IMAGE", direction: "OUT") deleted: Boolean disabled: Boolean - role: UserGroup! + role: UserRole! publicKey: String invitedBy: User @relation(name: "INVITED", direction: "IN") invited: [User] @relation(name: "INVITED", direction: "OUT") @@ -151,7 +151,7 @@ input _UserFilter { followedBy_none: _UserFilter followedBy_single: _UserFilter followedBy_every: _UserFilter - role_in: [UserGroup!] + role_in: [UserRole!] } type Query { @@ -160,7 +160,7 @@ type Query { email: String # admins need to search for a user sometimes name: String slug: String - role: UserGroup + role: UserRole locationName: String about: String createdAt: String @@ -171,7 +171,7 @@ type Query { filter: _UserFilter ): [User] - availableRoles: [UserGroup]! + availableRoles: [UserRole]! mutedUsers: [User] blockedUsers: [User] isLoggedIn: Boolean! @@ -219,5 +219,5 @@ type Mutation { blockUser(id: ID!): User unblockUser(id: ID!): User - switchUserRole(role: UserGroup!, id: ID!): User + switchUserRole(role: UserRole!, id: ID!): User } diff --git a/webapp/graphql/admin/Roles.js b/webapp/graphql/admin/Roles.js index 8c1a9f412..4365cb533 100644 --- a/webapp/graphql/admin/Roles.js +++ b/webapp/graphql/admin/Roles.js @@ -10,7 +10,7 @@ export const FetchAllRoles = () => { export const updateUserRole = (role, id) => { return gql` - mutation($role: UserGroup!, $id: ID!) { + mutation($role: UserRole!, $id: ID!) { switchUserRole(role: $role, id: $id) { name role From 117bd5e4e6e49eda5483ff1bc34dd833dc9b1b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 9 Aug 2022 16:34:50 +0200 Subject: [PATCH 02/10] Implement error for to short 'description' and test it --- backend/src/constants/categories.js | 8 +++--- backend/src/constants/groups.js | 2 ++ backend/src/middleware/helpers/cleanHtml.js | 7 +++++ backend/src/middleware/languages/languages.js | 9 +------ backend/src/schema/resolvers/groups.js | 15 ++++++++--- backend/src/schema/resolvers/groups.spec.js | 27 ++++++++++++++++--- webapp/constants/categories.js | 3 +++ webapp/constants/groups.js | 2 ++ 8 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 backend/src/constants/groups.js create mode 100644 webapp/constants/categories.js create mode 100644 webapp/constants/groups.js diff --git a/backend/src/constants/categories.js b/backend/src/constants/categories.js index 37cac8151..64ceb9021 100644 --- a/backend/src/constants/categories.js +++ b/backend/src/constants/categories.js @@ -1,5 +1,3 @@ -// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js` -export default { - CATEGORIES_MIN: 1, - CATEGORIES_MAX: 3, -} +// this file is duplicated in `backend/src/constants/metadata.js` and `webapp/constants/metadata.js` +export const CATEGORIES_MIN = 1 +export const CATEGORIES_MAX = 3 diff --git a/backend/src/constants/groups.js b/backend/src/constants/groups.js new file mode 100644 index 000000000..b4a6063f1 --- /dev/null +++ b/backend/src/constants/groups.js @@ -0,0 +1,2 @@ +// 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 diff --git a/backend/src/middleware/helpers/cleanHtml.js b/backend/src/middleware/helpers/cleanHtml.js index 72976b43c..ac71f6bdc 100644 --- a/backend/src/middleware/helpers/cleanHtml.js +++ b/backend/src/middleware/helpers/cleanHtml.js @@ -1,6 +1,13 @@ import sanitizeHtml from 'sanitize-html' import linkifyHtml from 'linkifyjs/html' +export const removeHtmlTags = (input) => { + return sanitizeHtml(input, { + allowedTags: [], + allowedAttributes: {}, + }) +} + const standardSanitizeHtmlOptions = { allowedTags: [ 'img', diff --git a/backend/src/middleware/languages/languages.js b/backend/src/middleware/languages/languages.js index 3cf760f31..087252975 100644 --- a/backend/src/middleware/languages/languages.js +++ b/backend/src/middleware/languages/languages.js @@ -1,12 +1,5 @@ import LanguageDetect from 'languagedetect' -import sanitizeHtml from 'sanitize-html' - -const removeHtmlTags = (input) => { - return sanitizeHtml(input, { - allowedTags: [], - allowedAttributes: {}, - }) -} +import { removeHtmlTags } from '../helpers/cleanHtml.js' const setPostLanguage = (text) => { const lngDetector = new LanguageDetect() diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index a958e990e..0e07b7542 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -3,7 +3,9 @@ import { v4 as uuid } from 'uuid' // Wolle: import { isEmpty } from 'lodash' import { UserInputError } from 'apollo-server' import CONFIG from '../../config' -import categories from '../../constants/categories' +import { CATEGORIES_MIN, CATEGORIES_MAX } from '../../constants/categories' +import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '../../constants/groups' +import { removeHtmlTags } from '../../middleware/helpers/cleanHtml.js' // Wolle: import { mergeImage, deleteImage } from './images/images' import Resolver from './helpers/Resolver' // Wolle: import { filterForMutedUsers } from './helpers/filterForMutedUsers' @@ -70,12 +72,19 @@ export default { CreateGroup: async (_parent, params, context, _resolveInfo) => { const { categoryIds } = params delete params.categoryIds - if (!categoryIds || categoryIds.length < categories.CATEGORIES_MIN) { + if (!categoryIds || categoryIds.length < CATEGORIES_MIN) { throw new UserInputError('To Less Categories!') } - if (categoryIds && categoryIds.length > categories.CATEGORIES_MAX) { + if (categoryIds && categoryIds.length > CATEGORIES_MAX) { throw new UserInputError('To Many Categories!') } + if ( + params.description === undefined || + params.description === null || + removeHtmlTags(params.description).length < DESCRIPTION_WITHOUT_HTML_LENGTH_MIN + ) { + throw new UserInputError('To Short Description!') + } params.id = params.id || uuid() const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 8f20c4fa7..ad9b6d68e 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -13,6 +13,8 @@ let authenticatedUser let user const categoryIds = ['cat9', 'cat4', 'cat15'] +const descriptionAddition100 = + ' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789' let variables = {} beforeAll(async () => { @@ -116,7 +118,7 @@ describe('Group', () => { id: 'others-group', name: 'Uninteresting Group', about: 'We will change nothing!', - description: 'We love it like it is!?', + description: 'We love it like it is!?' + descriptionAddition100, groupType: 'closed', actionRadius: 'international', categoryIds, @@ -129,7 +131,7 @@ describe('Group', () => { id: 'my-group', name: 'The Best Group', about: 'We will change the world!', - description: 'Some description', + description: 'Some description' + descriptionAddition100, groupType: 'public', actionRadius: 'regional', categoryIds, @@ -363,7 +365,7 @@ describe('CreateGroup', () => { name: 'The Best Group', slug: 'the-group', about: 'We will change the world!', - description: 'Some description', + description: 'Some description' + descriptionAddition100, groupType: 'public', actionRadius: 'regional', categoryIds, @@ -423,6 +425,25 @@ describe('CreateGroup', () => { ) }) + describe('description', () => { + describe('length without HTML', () => { + describe('less then 100 chars', () => { + it('throws error: "To Less Categories!"', async () => { + const { errors } = await mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + description: + '0123456789' + + '0123456789', + }, + }) + expect(errors[0]).toHaveProperty('message', 'To Short Description!') + }) + }) + }) + }) + describe('categories', () => { describe('not even one', () => { it('throws error: "To Less Categories!"', async () => { diff --git a/webapp/constants/categories.js b/webapp/constants/categories.js new file mode 100644 index 000000000..64ceb9021 --- /dev/null +++ b/webapp/constants/categories.js @@ -0,0 +1,3 @@ +// this file is duplicated in `backend/src/constants/metadata.js` and `webapp/constants/metadata.js` +export const CATEGORIES_MIN = 1 +export const CATEGORIES_MAX = 3 diff --git a/webapp/constants/groups.js b/webapp/constants/groups.js new file mode 100644 index 000000000..b4a6063f1 --- /dev/null +++ b/webapp/constants/groups.js @@ -0,0 +1,2 @@ +// 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 From 0149af12d4c09ca8ec3f99c62a746609bacf6e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 9 Aug 2022 16:48:46 +0200 Subject: [PATCH 03/10] Cleanup --- backend/src/db/graphql/groups.js | 12 - backend/src/models/Group.js | 100 +---- backend/src/schema/resolvers/groups.js | 112 ----- backend/src/schema/resolvers/groups.spec.js | 458 -------------------- backend/src/schema/types/type/Group.gql | 164 +------ 5 files changed, 3 insertions(+), 843 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index c41f06e4d..2a611f324 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -35,9 +35,6 @@ export const createGroupMutation = gql` groupType actionRadius myRole - # Wolle: owner { - # name - # } } } ` @@ -54,9 +51,6 @@ export const groupQuery = gql` $updatedAt: String $about: String $description: String - # $groupType: GroupType!, - # $actionRadius: GroupActionRadius!, - # $categoryIds: [ID] $locationName: String $first: Int $offset: Int @@ -72,9 +66,6 @@ export const groupQuery = gql` updatedAt: $updatedAt about: $about description: $description - # groupType: $groupType - # actionRadius: $actionRadius - # categoryIds: $categoryIds locationName: $locationName first: $first offset: $offset @@ -99,9 +90,6 @@ export const groupQuery = gql` name icon } - # Wolle: owner { - # name - # } } } ` diff --git a/backend/src/models/Group.js b/backend/src/models/Group.js index 0cec02bf8..25149e9c3 100644 --- a/backend/src/models/Group.js +++ b/backend/src/models/Group.js @@ -38,109 +38,11 @@ export default { locationName: { type: 'string', allow: [null] }, wasSeeded: 'boolean', // Wolle: used or needed? - // Wolle: owner: { - // type: 'relationship', - // relationship: 'OWNS', - // target: 'User', - // direction: 'in', - // }, - // Wolle: followedBy: { - // type: 'relationship', - // relationship: 'FOLLOWS', - // target: 'User', - // direction: 'in', - // properties: { - // createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - // }, - // }, - // Wolle: correct this way? - // members: { type: 'relationship', relationship: 'MEMBERS', target: 'User', direction: 'out' }, - // Wolle: needed? lastActiveAt: { type: 'string', isoDate: true }, - // Wolle: emoted: { - // type: 'relationships', - // relationship: 'EMOTED', - // target: 'Post', - // direction: 'out', - // properties: { - // emotion: { - // type: 'string', - // valid: ['happy', 'cry', 'surprised', 'angry', 'funny'], - // invalid: [null], - // }, - // }, - // eager: true, - // cascade: true, - // }, - // Wolle: blocked: { - // type: 'relationship', - // relationship: 'BLOCKED', - // target: 'User', - // direction: 'out', - // properties: { - // createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - // }, - // }, - // Wolle: muted: { - // type: 'relationship', - // relationship: 'MUTED', - // target: 'User', - // direction: 'out', - // properties: { - // createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - // }, - // }, - // Wolle: notifications: { - // type: 'relationship', - // relationship: 'NOTIFIED', - // target: 'User', - // direction: 'in', - // }, - // Wolle inviteCodes: { - // type: 'relationship', - // relationship: 'GENERATED', - // target: 'InviteCode', - // direction: 'out', - // }, - // Wolle: redeemedInviteCode: { - // type: 'relationship', - // relationship: 'REDEEMED', - // target: 'InviteCode', - // direction: 'out', - // }, - // Wolle: shouted: { - // type: 'relationship', - // relationship: 'SHOUTED', - // target: 'Post', - // direction: 'out', - // properties: { - // createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - // }, - // }, + isIn: { type: 'relationship', relationship: 'IS_IN', target: 'Location', direction: 'out', }, - // Wolle: pinned: { - // type: 'relationship', - // relationship: 'PINNED', - // target: 'Post', - // direction: 'out', - // properties: { - // createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - // }, - // }, - // Wolle: showShoutsPublicly: { - // type: 'boolean', - // default: false, - // }, - // Wolle: sendNotificationEmails: { - // type: 'boolean', - // default: true, - // }, - // Wolle: locale: { - // type: 'string', - // allow: [null], - // }, } diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 0e07b7542..75f9e35df 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -1,32 +1,13 @@ import { v4 as uuid } from 'uuid' -// Wolle: import { neo4jgraphql } from 'neo4j-graphql-js' -// Wolle: import { isEmpty } from 'lodash' import { UserInputError } from 'apollo-server' import CONFIG from '../../config' import { CATEGORIES_MIN, CATEGORIES_MAX } from '../../constants/categories' import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '../../constants/groups' import { removeHtmlTags } from '../../middleware/helpers/cleanHtml.js' -// Wolle: import { mergeImage, deleteImage } from './images/images' import Resolver from './helpers/Resolver' -// Wolle: import { filterForMutedUsers } from './helpers/filterForMutedUsers' - -// Wolle: const maintainPinnedPosts = (params) => { -// const pinnedPostFilter = { pinned: true } -// if (isEmpty(params.filter)) { -// params.filter = { OR: [pinnedPostFilter, {}] } -// } else { -// params.filter = { OR: [pinnedPostFilter, { ...params.filter }] } -// } -// return params -// } export default { Query: { - // Wolle: Post: async (object, params, context, resolveInfo) => { - // params = await filterForMutedUsers(params, context) - // // params = await maintainPinnedPosts(params) - // return neo4jgraphql(object, params, context, resolveInfo) - // }, Group: async (_object, params, context, _resolveInfo) => { const { isMember } = params const session = context.driver.session() @@ -130,105 +111,12 @@ export default { session.close() } }, - // UpdatePost: async (_parent, params, context, _resolveInfo) => { - // const { categoryIds } = params - // const { image: imageInput } = params - // delete params.categoryIds - // delete params.image - // const session = context.driver.session() - // let updatePostCypher = ` - // MATCH (post:Post {id: $params.id}) - // SET post += $params - // SET post.updatedAt = toString(datetime()) - // WITH post - // ` - - // if (categoryIds && categoryIds.length) { - // const cypherDeletePreviousRelations = ` - // MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category) - // DELETE previousRelations - // RETURN post, category - // ` - - // await session.writeTransaction((transaction) => { - // return transaction.run(cypherDeletePreviousRelations, { params }) - // }) - - // updatePostCypher += ` - // UNWIND $categoryIds AS categoryId - // MATCH (category:Category {id: categoryId}) - // MERGE (post)-[:CATEGORIZED]->(category) - // WITH post - // ` - // } - - // updatePostCypher += `RETURN post {.*}` - // const updatePostVariables = { categoryIds, params } - // try { - // const writeTxResultPromise = session.writeTransaction(async (transaction) => { - // const updatePostTransactionResponse = await transaction.run( - // updatePostCypher, - // updatePostVariables, - // ) - // const [post] = updatePostTransactionResponse.records.map((record) => record.get('post')) - // await mergeImage(post, 'HERO_IMAGE', imageInput, { transaction }) - // return post - // }) - // const post = await writeTxResultPromise - // return post - // } finally { - // session.close() - // } - // }, - - // DeletePost: async (object, args, context, resolveInfo) => { - // const session = context.driver.session() - // const writeTxResultPromise = session.writeTransaction(async (transaction) => { - // const deletePostTransactionResponse = await transaction.run( - // ` - // MATCH (post:Post {id: $postId}) - // OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment) - // SET post.deleted = TRUE - // SET post.content = 'UNAVAILABLE' - // SET post.contentExcerpt = 'UNAVAILABLE' - // SET post.title = 'UNAVAILABLE' - // SET comment.deleted = TRUE - // RETURN post {.*} - // `, - // { postId: args.id }, - // ) - // const [post] = deletePostTransactionResponse.records.map((record) => record.get('post')) - // await deleteImage(post, 'HERO_IMAGE', { transaction }) - // return post - // }) - // try { - // const post = await writeTxResultPromise - // return post - // } finally { - // session.close() - // } }, Group: { ...Resolver('Group', { - // Wolle: undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'], hasMany: { - // Wolle: tags: '-[:TAGGED]->(related:Tag)', categories: '-[:CATEGORIZED]->(related:Category)', }, - // hasOne: { - // owner: '<-[:OWNS]-(related:User)', - // // Wolle: image: '-[:HERO_IMAGE]->(related:Image)', - // }, - // Wolle: count: { - // contributionsCount: - // '-[:WROTE]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true', - // }, - // Wolle: boolean: { - // shoutedByCurrentUser: - // 'MATCH(this)<-[:SHOUTED]-(related:User {id: $cypherParams.currentUserId}) RETURN COUNT(related) >= 1', - // viewedTeaserByCurrentUser: - // 'MATCH (this)<-[:VIEWED_TEASER]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', - // }, }), }, } diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index ad9b6d68e..bae530c61 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -197,163 +197,6 @@ describe('Group', () => { ).resolves.toMatchObject(expected) }) }) - - // describe('can be filtered', () => { - // Wolle: it('by categories', async () => { - // const postQueryFilteredByCategories = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // categories { - // id - // } - // } - // } - // ` - // const expected = { - // data: { - // Post: [ - // { - // id: 'post-by-followed-user', - // categories: [{ id: 'cat9' }], - // }, - // ], - // }, - // } - // variables = { ...variables, filter: { categories_some: { id_in: ['cat9'] } } } - // await expect( - // query({ query: postQueryFilteredByCategories, variables }), - // ).resolves.toMatchObject(expected) - // }) - // Wolle: let followedUser, happyPost, cryPost - // beforeEach(async () => { - // ;[followedUser] = await Promise.all([ - // Factory.build( - // 'user', - // { - // id: 'followed-by-me', - // name: 'Followed User', - // }, - // { - // email: 'followed@example.org', - // password: '1234', - // }, - // ), - // ]) - // ;[happyPost, cryPost] = await Promise.all([ - // Factory.build('post', { id: 'happy-post' }, { categoryIds: ['cat4'] }), - // Factory.build('post', { id: 'cry-post' }, { categoryIds: ['cat15'] }), - // Factory.build( - // 'post', - // { - // id: 'post-by-followed-user', - // }, - // { - // categoryIds: ['cat9'], - // author: followedUser, - // }, - // ), - // ]) - // }) - // describe('no filter', () => { - // it('returns all posts', async () => { - // const postQueryNoFilters = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // } - // } - // ` - // const expected = [{ id: 'happy-post' }, { id: 'cry-post' }, { id: 'post-by-followed-user' }] - // variables = { filter: {} } - // await expect(query({ query: postQueryNoFilters, variables })).resolves.toMatchObject({ - // data: { - // Post: expect.arrayContaining(expected), - // }, - // }) - // }) - // }) - // describe('by emotions', () => { - // const postQueryFilteredByEmotions = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // emotions { - // emotion - // } - // } - // } - // ` - // it('filters by single emotion', async () => { - // const expected = { - // data: { - // Post: [ - // { - // id: 'happy-post', - // emotions: [{ emotion: 'happy' }], - // }, - // ], - // }, - // } - // await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) - // variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy'] } } } - // await expect( - // query({ query: postQueryFilteredByEmotions, variables }), - // ).resolves.toMatchObject(expected) - // }) - // it('filters by multiple emotions', async () => { - // const expected = [ - // { - // id: 'happy-post', - // emotions: [{ emotion: 'happy' }], - // }, - // { - // id: 'cry-post', - // emotions: [{ emotion: 'cry' }], - // }, - // ] - // await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) - // await user.relateTo(cryPost, 'emoted', { emotion: 'cry' }) - // variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy', 'cry'] } } } - // await expect( - // query({ query: postQueryFilteredByEmotions, variables }), - // ).resolves.toMatchObject({ - // data: { - // Post: expect.arrayContaining(expected), - // }, - // errors: undefined, - // }) - // }) - // }) - // it('by followed-by', async () => { - // const postQueryFilteredByUsersFollowed = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // author { - // id - // name - // } - // } - // } - // ` - // await user.relateTo(followedUser, 'following') - // variables = { filter: { author: { followedBy_some: { id: 'current-user' } } } } - // await expect( - // query({ query: postQueryFilteredByUsersFollowed, variables }), - // ).resolves.toMatchObject({ - // data: { - // Post: [ - // { - // id: 'post-by-followed-user', - // author: { id: 'followed-by-me', name: 'Followed User' }, - // }, - // ], - // }, - // errors: undefined, - // }) - // }) - // }) }) }) @@ -406,9 +249,6 @@ describe('CreateGroup', () => { CreateGroup: { name: 'The Best Group', myRole: 'owner', - // Wolle: owner: { - // name: 'TestUser', - // }, }, }, errors: undefined, @@ -467,301 +307,3 @@ describe('CreateGroup', () => { }) }) }) - -// describe('UpdatePost', () => { -// let author, newlyCreatedPost -// const updatePostMutation = gql` -// mutation ($id: ID!, $title: String!, $content: String!, $image: ImageInput) { -// UpdatePost(id: $id, title: $title, content: $content, image: $image) { -// id -// title -// content -// author { -// name -// slug -// } -// createdAt -// updatedAt -// } -// } -// ` -// beforeEach(async () => { -// author = await Factory.build('user', { slug: 'the-author' }) -// newlyCreatedPost = await Factory.build( -// 'post', -// { -// id: 'p9876', -// title: 'Old title', -// content: 'Old content', -// }, -// { -// author, -// categoryIds, -// }, -// ) - -// variables = { -// id: 'p9876', -// title: 'New title', -// content: 'New content', -// } -// }) - -// describe('unauthenticated', () => { -// it('throws authorization error', async () => { -// authenticatedUser = null -// expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ -// errors: [{ message: 'Not Authorised!' }], -// data: { UpdatePost: null }, -// }) -// }) -// }) - -// describe('authenticated but not the author', () => { -// beforeEach(async () => { -// authenticatedUser = await user.toJson() -// }) - -// it('throws authorization error', async () => { -// const { errors } = await mutate({ mutation: updatePostMutation, variables }) -// expect(errors[0]).toHaveProperty('message', 'Not Authorised!') -// }) -// }) - -// describe('authenticated as author', () => { -// beforeEach(async () => { -// authenticatedUser = await author.toJson() -// }) - -// it('updates a post', async () => { -// const expected = { -// data: { UpdatePost: { id: 'p9876', content: 'New content' } }, -// errors: undefined, -// } -// await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( -// expected, -// ) -// }) - -// it('updates a post, but maintains non-updated attributes', async () => { -// const expected = { -// data: { -// UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) }, -// }, -// errors: undefined, -// } -// await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( -// expected, -// ) -// }) - -// it('updates the updatedAt attribute', async () => { -// newlyCreatedPost = await newlyCreatedPost.toJson() -// const { -// data: { UpdatePost }, -// } = await mutate({ mutation: updatePostMutation, variables }) -// expect(newlyCreatedPost.updatedAt).toBeTruthy() -// expect(Date.parse(newlyCreatedPost.updatedAt)).toEqual(expect.any(Number)) -// expect(UpdatePost.updatedAt).toBeTruthy() -// expect(Date.parse(UpdatePost.updatedAt)).toEqual(expect.any(Number)) -// expect(newlyCreatedPost.updatedAt).not.toEqual(UpdatePost.updatedAt) -// }) - -// /* describe('no new category ids provided for update', () => { -// it('resolves and keeps current categories', async () => { -// const expected = { -// data: { -// UpdatePost: { -// id: 'p9876', -// categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]), -// }, -// }, -// errors: undefined, -// } -// await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( -// expected, -// ) -// }) -// }) */ - -// /* describe('given category ids', () => { -// beforeEach(() => { -// variables = { ...variables, categoryIds: ['cat27'] } -// }) - -// it('updates categories of a post', async () => { -// const expected = { -// data: { -// UpdatePost: { -// id: 'p9876', -// categories: expect.arrayContaining([{ id: 'cat27' }]), -// }, -// }, -// errors: undefined, -// } -// await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( -// expected, -// ) -// }) -// }) */ - -// describe('params.image', () => { -// describe('is object', () => { -// beforeEach(() => { -// variables = { ...variables, image: { sensitive: true } } -// }) -// it('updates the image', async () => { -// await expect(neode.first('Image', { sensitive: true })).resolves.toBeFalsy() -// await mutate({ mutation: updatePostMutation, variables }) -// await expect(neode.first('Image', { sensitive: true })).resolves.toBeTruthy() -// }) -// }) - -// describe('is null', () => { -// beforeEach(() => { -// variables = { ...variables, image: null } -// }) -// it('deletes the image', async () => { -// await expect(neode.all('Image')).resolves.toHaveLength(6) -// await mutate({ mutation: updatePostMutation, variables }) -// await expect(neode.all('Image')).resolves.toHaveLength(5) -// }) -// }) - -// describe('is undefined', () => { -// beforeEach(() => { -// delete variables.image -// }) -// it('keeps the image unchanged', async () => { -// await expect(neode.first('Image', { sensitive: true })).resolves.toBeFalsy() -// await mutate({ mutation: updatePostMutation, variables }) -// await expect(neode.first('Image', { sensitive: true })).resolves.toBeFalsy() -// }) -// }) -// }) -// }) -// }) - -// describe('DeletePost', () => { -// let author -// const deletePostMutation = gql` -// mutation ($id: ID!) { -// DeletePost(id: $id) { -// id -// deleted -// content -// contentExcerpt -// image { -// url -// } -// comments { -// deleted -// content -// contentExcerpt -// } -// } -// } -// ` - -// beforeEach(async () => { -// author = await Factory.build('user') -// await Factory.build( -// 'post', -// { -// id: 'p4711', -// title: 'I will be deleted', -// content: 'To be deleted', -// }, -// { -// image: Factory.build('image', { -// url: 'path/to/some/image', -// }), -// author, -// categoryIds, -// }, -// ) -// variables = { ...variables, id: 'p4711' } -// }) - -// describe('unauthenticated', () => { -// it('throws authorization error', async () => { -// const { errors } = await mutate({ mutation: deletePostMutation, variables }) -// expect(errors[0]).toHaveProperty('message', 'Not Authorised!') -// }) -// }) - -// describe('authenticated but not the author', () => { -// beforeEach(async () => { -// authenticatedUser = await user.toJson() -// }) - -// it('throws authorization error', async () => { -// const { errors } = await mutate({ mutation: deletePostMutation, variables }) -// expect(errors[0]).toHaveProperty('message', 'Not Authorised!') -// }) -// }) - -// describe('authenticated as author', () => { -// beforeEach(async () => { -// authenticatedUser = await author.toJson() -// }) - -// it('marks the post as deleted and blacks out attributes', async () => { -// const expected = { -// data: { -// DeletePost: { -// id: 'p4711', -// deleted: true, -// content: 'UNAVAILABLE', -// contentExcerpt: 'UNAVAILABLE', -// image: null, -// comments: [], -// }, -// }, -// } -// await expect(mutate({ mutation: deletePostMutation, variables })).resolves.toMatchObject( -// expected, -// ) -// }) - -// describe('if there are comments on the post', () => { -// beforeEach(async () => { -// await Factory.build( -// 'comment', -// { -// content: 'to be deleted comment content', -// contentExcerpt: 'to be deleted comment content', -// }, -// { -// postId: 'p4711', -// }, -// ) -// }) - -// it('marks the comments as deleted', async () => { -// const expected = { -// data: { -// DeletePost: { -// id: 'p4711', -// deleted: true, -// content: 'UNAVAILABLE', -// contentExcerpt: 'UNAVAILABLE', -// image: null, -// comments: [ -// { -// deleted: true, -// // Should we black out the comment content in the database, too? -// content: 'UNAVAILABLE', -// contentExcerpt: 'UNAVAILABLE', -// }, -// ], -// }, -// }, -// } -// await expect(mutate({ mutation: deletePostMutation, variables })).resolves.toMatchObject( -// expected, -// ) -// }) -// }) -// }) -// }) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 2dc20aebf..3165b4a44 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -13,8 +13,6 @@ enum _GroupOrdering { createdAt_desc updatedAt_asc updatedAt_desc - # Wolle: needed? locale_asc - # locale_desc } type Group { @@ -40,90 +38,6 @@ type Group { categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT") myRole: GroupMemberRole # if 'null' then the current user is no member - - # Wolle: needed? - # socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN") - - # Wolle: owner: User @relation(name: "OWNS", direction: "IN") - - # Wolle: showShoutsPublicly: Boolean - # Wolle: sendNotificationEmails: Boolean - # Wolle: needed? locale: String - # members: [User]! @relation(name: "MEMBERS", direction: "OUT") - # membersCount: Int! - # @cypher(statement: "MATCH (this)-[:MEMBERS]->(r:User) RETURN COUNT(DISTINCT r)") - - # Wolle: followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") - # Wolle: followedByCount: Int! - # @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)") - - # Wolle: inviteCodes: [InviteCode] @relation(name: "GENERATED", direction: "OUT") - # Wolle: redeemedInviteCode: InviteCode @relation(name: "REDEEMED", direction: "OUT") - - # Is the currently logged in user following that user? - # Wolle: followedByCurrentUser: Boolean! - # @cypher( - # statement: """ - # MATCH (this)<-[:FOLLOWS]-(u:User { id: $cypherParams.currentUserId}) - # RETURN COUNT(u) >= 1 - # """ - # ) - - # Wolle: isBlocked: Boolean! - # @cypher( - # statement: """ - # MATCH (this)<-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId}) - # RETURN COUNT(user) >= 1 - # """ - # ) - # Wolle: blocked: Boolean! - # @cypher( - # statement: """ - # MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId}) - # RETURN COUNT(user) >= 1 - # """ - # ) - - # Wolle: isMuted: Boolean! - # @cypher( - # statement: """ - # MATCH (this)<-[:MUTED]-(user:User { id: $cypherParams.currentUserId}) - # RETURN COUNT(user) >= 1 - # """ - # ) - - # contributions: [WrittenPost]! - # contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! - # @cypher( - # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" - # ) - # Wolle: needed? - # contributions: [Post]! @relation(name: "WROTE", direction: "OUT") - # contributionsCount: Int! - # @cypher( - # statement: """ - # MATCH (this)-[:WROTE]->(r:Post) - # WHERE NOT r.deleted = true AND NOT r.disabled = true - # RETURN COUNT(r) - # """ - # ) - - # Wolle: comments: [Comment]! @relation(name: "WROTE", direction: "OUT") - # commentedCount: Int! - # @cypher( - # statement: "MATCH (this)-[:WROTE]->(:Comment)-[:COMMENTS]->(p:Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))" - # ) - - # Wolle: shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") - # shoutedCount: Int! - # @cypher( - # statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)" - # ) - - # Wolle: badges: [Badge]! @relation(name: "REWARDED", direction: "IN") - # badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") - - # Wolle: emotions: [EMOTED] } @@ -141,39 +55,6 @@ input _GroupFilter { id_not: ID id_in: [ID!] id_not_in: [ID!] - # categories: _CategoryFilter - # categories_not: _CategoryFilter - # categories_in: [_CategoryFilter!] - # categories_not_in: [_CategoryFilter!] - # categories_some: _CategoryFilter - # categories_none: _CategoryFilter - # categories_single: _CategoryFilter - # categories_every: _CategoryFilter - # Wolle: - # friends: _GroupFilter - # friends_not: _GroupFilter - # friends_in: [_GroupFilter!] - # friends_not_in: [_GroupFilter!] - # friends_some: _GroupFilter - # friends_none: _GroupFilter - # friends_single: _GroupFilter - # friends_every: _GroupFilter - # following: _GroupFilter - # following_not: _GroupFilter - # following_in: [_GroupFilter!] - # following_not_in: [_GroupFilter!] - # following_some: _GroupFilter - # following_none: _GroupFilter - # following_single: _GroupFilter - # following_every: _GroupFilter - # followedBy: _GroupFilter - # followedBy_not: _GroupFilter - # followedBy_in: [_GroupFilter!] - # followedBy_not_in: [_GroupFilter!] - # followedBy_some: _GroupFilter - # followedBy_none: _GroupFilter - # followedBy_single: _GroupFilter - # followedBy_every: _GroupFilter } type Query { @@ -198,32 +79,8 @@ type Query { AvailableGroupActionRadii: [GroupActionRadius]! AvailableGroupMemberRoles: [GroupMemberRole]! - - # Wolle: - # availableRoles: [UserRole]! - # mutedUsers: [User] - # blockedUsers: [User] - # isLoggedIn: Boolean! - # currentUser: User - # findUsers(query: String!,limit: Int = 10, filter: _GroupFilter): [User]! - # @cypher( - # statement: """ - # CALL db.index.fulltext.queryNodes('user_fulltext_search', $query) - # YIELD node as post, score - # MATCH (user) - # WHERE score >= 0.2 - # AND NOT user.deleted = true AND NOT user.disabled = true - # RETURN user - # LIMIT $limit - # """ - # ) } -# Wolle: enum Deletable { -# Post -# Comment -# } - type Mutation { CreateGroup( id: ID @@ -236,12 +93,7 @@ type Mutation { actionRadius: GroupActionRadius! categoryIds: [ID] locationName: String - ): # Wolle: add group settings - # Wolle: - # showShoutsPublicly: Boolean - # sendNotificationEmails: Boolean - # locale: String - Group + ): Group UpdateGroup( id: ID! @@ -251,19 +103,7 @@ type Mutation { locationName: String about: String description: String - ): # Wolle: - # showShoutsPublicly: Boolean - # sendNotificationEmails: Boolean - # locale: String - Group + ): Group DeleteGroup(id: ID!): Group - - # Wolle: - # muteUser(id: ID!): User - # unmuteUser(id: ID!): User - # blockUser(id: ID!): User - # unblockUser(id: ID!): User - - # Wolle: switchUserRole(role: UserRole!, id: ID!): User } From 3d1b403656969929b4f2163f4d3d095cc961c5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 9 Aug 2022 16:55:43 +0200 Subject: [PATCH 04/10] Add relation 'CREATED' between owner and group --- backend/src/schema/resolvers/groups.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 75f9e35df..e6a8c3a18 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -86,6 +86,7 @@ export default { SET group.updatedAt = toString(datetime()) WITH group MATCH (owner:User {id: $userId}) + MERGE (owner)-[:CREATED]->(group) MERGE (owner)-[membership:MEMBER_OF]->(group) SET membership.createdAt = toString(datetime()) SET membership.updatedAt = toString(datetime()) From 9cae32e6e13ce27a01b53e2641c5a937361eb401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 10 Aug 2022 08:41:01 +0200 Subject: [PATCH 05/10] Upgrade neode to v0.4.8 --- backend/package.json | 2 +- backend/yarn.lock | 83 +++++++++++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/backend/package.json b/backend/package.json index 62188a650..9651cbb95 100644 --- a/backend/package.json +++ b/backend/package.json @@ -103,7 +103,7 @@ "mustache": "^4.2.0", "neo4j-driver": "^4.0.2", "neo4j-graphql-js": "^2.11.5", - "neode": "^0.4.7", + "neode": "^0.4.8", "node-fetch": "~2.6.1", "nodemailer": "^6.4.4", "nodemailer-html-to-text": "^3.2.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index 24bd00b3a..8c69a0814 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -997,9 +997,9 @@ tslib "1.11.1" "@hapi/address@2.x.x": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.2.tgz#1c794cd6dbf2354d1eb1ef10e0303f573e1c7222" - integrity sha512-O4QDrx+JoGKZc6aN64L04vqa7e41tIiLU+OvKdcYaEMP97UttL0f9GIi9/0A4WAMx0uBd6SidDIhktZhgOcN8Q== + version "2.1.4" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== "@hapi/address@^4.0.1": version "4.0.1" @@ -1018,10 +1018,10 @@ resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== -"@hapi/hoek@8.x.x": - version "8.2.4" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.2.4.tgz#684a14f4ca35d46f44abc87dfc696e5e4fe8a020" - integrity sha512-Ze5SDNt325yZvNO7s5C4fXDscjJ6dcqLFXJQ/M7dZRQCewuDj2iDUuBi6jLQt+APbW9RjjVEvLr35FXuOEqjow== +"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" + integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== "@hapi/hoek@^9.0.0": version "9.0.0" @@ -1055,11 +1055,11 @@ integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== "@hapi/topo@3.x.x": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.3.tgz#c7a02e0d936596d29f184e6d7fdc07e8b5efce11" - integrity sha512-JmS9/vQK6dcUYn7wc2YZTqzIKubAQcJKu2KCKAru6es482U5RT5fP1EXCPtlXpiK7PR0On/kpQKI4fRKkzpZBQ== + version "3.1.6" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "^8.3.0" "@hapi/topo@^5.0.0": version "5.0.0" @@ -2681,6 +2681,11 @@ base64-js@^1.0.2: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -2850,6 +2855,14 @@ buffer@4.9.1: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + busboy@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" @@ -3929,7 +3942,7 @@ dot-prop@^4.1.0: dotenv@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" - integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= + integrity sha512-XcaMACOr3JMVcEv0Y/iUM2XaOsATRZ3U1In41/1jjK6vJZ2PZbQ1bzCG8uvaByfaBpl9gqc9QWJovpUGBXLLYQ== dotenv@^6.1.0: version "6.2.0" @@ -5516,6 +5529,11 @@ ieee754@1.1.13, ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ienoopen@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.1.0.tgz#411e5d530c982287dbdc3bb31e7a9c9e32630974" @@ -7528,18 +7546,19 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo4j-driver-bolt-connection@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-4.3.4.tgz#de642bb6a62ffc6ae2e280dccf21395b4d1705a2" - integrity sha512-yxbvwGav+N7EYjcEAINqL6D3CZV+ee2qLInpAhx+iNurwbl3zqtBGiVP79SZ+7tU++y3Q1fW5ofikH06yc+LqQ== +neo4j-driver-bolt-connection@^4.4.7: + version "4.4.7" + resolved "https://registry.yarnpkg.com/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-4.4.7.tgz#0582d54de1f213e60c374209193d1f645ba523ea" + integrity sha512-6Q4hCtvWE6gzN64N09UqZqf/3rDl7FUWZZXiVQL0ZRbaMkJpZNC2NmrDIgGXYE05XEEbRBexf2tVv5OTYZYrow== dependencies: - neo4j-driver-core "^4.3.4" - text-encoding-utf-8 "^1.0.2" + buffer "^6.0.3" + neo4j-driver-core "^4.4.7" + string_decoder "^1.3.0" -neo4j-driver-core@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/neo4j-driver-core/-/neo4j-driver-core-4.3.4.tgz#b445a4fbf94dce8441075099bd6ac3133c1cf5ee" - integrity sha512-3tn3j6IRUNlpXeehZ9Xv7dLTZPB4a7APaoJ+xhQyMmYQO3ujDM4RFHc0pZcG+GokmaltT5pUCIPTDYx6ODdhcA== +neo4j-driver-core@^4.4.7: + version "4.4.7" + resolved "https://registry.yarnpkg.com/neo4j-driver-core/-/neo4j-driver-core-4.4.7.tgz#d2475e107b3fea2b9d1c36b0c273da5c5a291c37" + integrity sha512-NhvVuQYgG7eO/vXxRaoJfkWUNkjvIpmCIS9UWU9Bbhb4V+wCOyX/MVOXqD0Yizhs4eyIkD7x90OXb79q+vi+oA== neo4j-driver@^4.0.1, neo4j-driver@^4.0.2: version "4.0.2" @@ -7552,13 +7571,13 @@ neo4j-driver@^4.0.1, neo4j-driver@^4.0.2: uri-js "^4.2.2" neo4j-driver@^4.2.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-4.3.4.tgz#a54f0562f868ee94dff7509df74e3eb2c1f95a85" - integrity sha512-AGrsFFqnoZv4KhJdmKt4mOBV5mnxmV3+/t8KJTOM68jQuEWoy+RlmAaRRaCSU4eY586OFN/R8lg9MrJpZdSFjw== + version "4.4.7" + resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-4.4.7.tgz#51b3fb48241e66eb3be94e90032cc494c44e59f3" + integrity sha512-N7GddPhp12gVJe4eB84u5ik5SmrtRv8nH3rK47Qy7IUKnJkVEos/F1QjOJN6zt1jLnDXwDcGzCKK8XklYpzogw== dependencies: "@babel/runtime" "^7.5.5" - neo4j-driver-bolt-connection "^4.3.4" - neo4j-driver-core "^4.3.4" + neo4j-driver-bolt-connection "^4.4.7" + neo4j-driver-core "^4.4.7" rxjs "^6.6.3" neo4j-graphql-js@^2.11.5: @@ -7574,10 +7593,10 @@ neo4j-graphql-js@^2.11.5: lodash "^4.17.15" neo4j-driver "^4.0.1" -neode@^0.4.7: - version "0.4.7" - resolved "https://registry.yarnpkg.com/neode/-/neode-0.4.7.tgz#033007b57a2ee167e9ee5537493086db08d005eb" - integrity sha512-YXlc187JRpeKCBcUIkY6nimXXG+Tvlopfe71/FPno2THrwmYt5mm0RPHZ+mXF2O1Xg6zvjKvOpCpDz2vHBfroQ== +neode@^0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/neode/-/neode-0.4.8.tgz#0889b4fc7f1bf0b470b01fa5b8870373b5d47ad6" + integrity sha512-pb91NfCOg4Fj5o+98H+S2XYC+ByQfbdhwcc1UVuzuUQ0Ezzj+jWz8NmKWU8ZfCH6l4plk71yDAPd2eTwpt+Xvg== dependencies: "@hapi/joi" "^15.1.1" dotenv "^4.0.0" @@ -9603,7 +9622,7 @@ string.prototype.trimstart@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== From 7682aa7e45a289df8018eba72442ff197f5234ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 10 Aug 2022 11:34:51 +0200 Subject: [PATCH 06/10] Fix description length for slugify tests --- backend/src/middleware/slugifyMiddleware.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 3c18e70b0..59fa72ba7 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -12,6 +12,8 @@ let variables const driver = getDriver() const neode = getNeode() +const descriptionAddition100 = + ' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789' beforeAll(async () => { await cleanDatabase() @@ -67,7 +69,7 @@ describe('slugifyMiddleware', () => { ...variables, name: 'The Best Group', about: 'Some about', - description: 'Some description', + description: 'Some description' + descriptionAddition100, groupType: 'closed', actionRadius: 'national', categoryIds, @@ -87,7 +89,7 @@ describe('slugifyMiddleware', () => { name: 'The Best Group', slug: 'the-best-group', about: 'Some about', - description: 'Some description', + description: 'Some description' + descriptionAddition100, groupType: 'closed', actionRadius: 'national', }, From 82401b1488dd6aee9282b6b9f810f480f25edf2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 10 Aug 2022 12:52:20 +0200 Subject: [PATCH 07/10] Update backend/src/schema/resolvers/groups.js Co-authored-by: Moriz Wahl --- backend/src/schema/resolvers/groups.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index e6a8c3a18..f6d482421 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -36,8 +36,7 @@ export default { const result = await txc.run(groupCypher, { userId: context.user.id, }) - const group = result.records.map((record) => record.get('group')) - return group + return result.records.map((record) => record.get('group')) }) try { const group = await readTxResultPromise From f150ea3d7ce24286127d2ddf8fa08ca977c5ded7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 10 Aug 2022 12:52:31 +0200 Subject: [PATCH 08/10] Update backend/src/schema/resolvers/groups.js Co-authored-by: Moriz Wahl --- backend/src/schema/resolvers/groups.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index f6d482421..dadbcd2a1 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -39,8 +39,7 @@ export default { return result.records.map((record) => record.get('group')) }) try { - const group = await readTxResultPromise - return group + return await readTxResultPromise } catch (error) { throw new Error(error) } finally { From 5e741ead8d3f96d5d4eacf88a7e7e3f70a361fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 10 Aug 2022 13:15:43 +0200 Subject: [PATCH 09/10] Overtake Moriz suggestions --- backend/src/models/Group.js | 2 - backend/src/schema/resolvers/groups.js | 6 +- backend/src/schema/resolvers/groups.spec.js | 126 +++++++++--------- .../schema/types/enum/GroupActionRadius.gql | 3 +- 4 files changed, 71 insertions(+), 66 deletions(-) diff --git a/backend/src/models/Group.js b/backend/src/models/Group.js index 25149e9c3..a75ad518f 100644 --- a/backend/src/models/Group.js +++ b/backend/src/models/Group.js @@ -37,8 +37,6 @@ export default { locationName: { type: 'string', allow: [null] }, - wasSeeded: 'boolean', // Wolle: used or needed? - isIn: { type: 'relationship', relationship: 'IS_IN', diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index dadbcd2a1..d1af98513 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -52,17 +52,17 @@ export default { const { categoryIds } = params delete params.categoryIds if (!categoryIds || categoryIds.length < CATEGORIES_MIN) { - throw new UserInputError('To Less Categories!') + throw new UserInputError('Too view categories!') } if (categoryIds && categoryIds.length > CATEGORIES_MAX) { - throw new UserInputError('To Many Categories!') + throw new UserInputError('Too many categories!') } if ( params.description === undefined || params.description === null || removeHtmlTags(params.description).length < DESCRIPTION_WITHOUT_HTML_LENGTH_MIN ) { - throw new UserInputError('To Short Description!') + throw new UserInputError('Description too short!') } params.id = params.id || uuid() const session = context.driver.session() diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index bae530c61..5354f5ebe 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -120,7 +120,7 @@ describe('Group', () => { about: 'We will change nothing!', description: 'We love it like it is!?' + descriptionAddition100, groupType: 'closed', - actionRadius: 'international', + actionRadius: 'global', categoryIds, }, }) @@ -139,62 +139,68 @@ describe('Group', () => { }) }) - describe('can find', () => { - it('all', async () => { - const expected = { - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }), - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), - }, - errors: undefined, - } - await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) + describe('query groups', () => { + describe('without any filters', () => { + it('finds all groups', async () => { + const expected = { + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }), + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + } + await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) + }) }) - it('where user is member (or owner in this case)', async () => { - const expected = { - data: { - Group: [ - { - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }, - ], - }, - errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: true } }), - ).resolves.toMatchObject(expected) + describe('isMember = true', () => { + it('finds only groups where user is member', async () => { + const expected = { + data: { + Group: [ + { + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }, + ], + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: true } }), + ).resolves.toMatchObject(expected) + }) }) - it('where user is not(!) member', async () => { - const expected = { - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), - }, - errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: false } }), - ).resolves.toMatchObject(expected) + describe('isMember = false', () => { + it('finds only groups where user is not(!) member', async () => { + const expected = { + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: false } }), + ).resolves.toMatchObject(expected) + }) }) }) }) @@ -258,7 +264,7 @@ describe('CreateGroup', () => { ) }) - it('"disabled" and "deleted" default to "false"', async () => { + it('has "disabled" and "deleted" default to "false"', async () => { const expected = { data: { CreateGroup: { disabled: false, deleted: false } } } await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( expected, @@ -268,7 +274,7 @@ describe('CreateGroup', () => { describe('description', () => { describe('length without HTML', () => { describe('less then 100 chars', () => { - it('throws error: "To Less Categories!"', async () => { + it('throws error: "Too view categories!"', async () => { const { errors } = await mutate({ mutation: createGroupMutation, variables: { @@ -278,7 +284,7 @@ describe('CreateGroup', () => { '0123456789', }, }) - expect(errors[0]).toHaveProperty('message', 'To Short Description!') + expect(errors[0]).toHaveProperty('message', 'Description too short!') }) }) }) @@ -286,22 +292,22 @@ describe('CreateGroup', () => { describe('categories', () => { describe('not even one', () => { - it('throws error: "To Less Categories!"', async () => { + it('throws error: "Too view categories!"', async () => { const { errors } = await mutate({ mutation: createGroupMutation, variables: { ...variables, categoryIds: null }, }) - expect(errors[0]).toHaveProperty('message', 'To Less Categories!') + expect(errors[0]).toHaveProperty('message', 'Too view categories!') }) }) describe('four', () => { - it('throws error: "To Many Categories!"', async () => { + it('throws error: "Too many categories!"', async () => { const { errors } = await mutate({ mutation: createGroupMutation, variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] }, }) - expect(errors[0]).toHaveProperty('message', 'To Many Categories!') + expect(errors[0]).toHaveProperty('message', 'Too many categories!') }) }) }) diff --git a/backend/src/schema/types/enum/GroupActionRadius.gql b/backend/src/schema/types/enum/GroupActionRadius.gql index afc421133..221ed7f87 100644 --- a/backend/src/schema/types/enum/GroupActionRadius.gql +++ b/backend/src/schema/types/enum/GroupActionRadius.gql @@ -2,5 +2,6 @@ enum GroupActionRadius { regional national continental - international + global + interplanetary } From b0d28f8649bba912a11263f206d6d368b579b648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 10 Aug 2022 13:19:42 +0200 Subject: [PATCH 10/10] Rename 'descriptionAddition100' to 'descriptionAdditional100' --- backend/src/middleware/slugifyMiddleware.spec.js | 6 +++--- backend/src/schema/resolvers/groups.spec.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 59fa72ba7..9605aada9 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -12,7 +12,7 @@ let variables const driver = getDriver() const neode = getNeode() -const descriptionAddition100 = +const descriptionAdditional100 = ' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789' beforeAll(async () => { @@ -69,7 +69,7 @@ describe('slugifyMiddleware', () => { ...variables, name: 'The Best Group', about: 'Some about', - description: 'Some description' + descriptionAddition100, + description: 'Some description' + descriptionAdditional100, groupType: 'closed', actionRadius: 'national', categoryIds, @@ -89,7 +89,7 @@ describe('slugifyMiddleware', () => { name: 'The Best Group', slug: 'the-best-group', about: 'Some about', - description: 'Some description' + descriptionAddition100, + description: 'Some description' + descriptionAdditional100, groupType: 'closed', actionRadius: 'national', }, diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 5354f5ebe..b3327d44a 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -13,7 +13,7 @@ let authenticatedUser let user const categoryIds = ['cat9', 'cat4', 'cat15'] -const descriptionAddition100 = +const descriptionAdditional100 = ' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789' let variables = {} @@ -118,7 +118,7 @@ describe('Group', () => { id: 'others-group', name: 'Uninteresting Group', about: 'We will change nothing!', - description: 'We love it like it is!?' + descriptionAddition100, + description: 'We love it like it is!?' + descriptionAdditional100, groupType: 'closed', actionRadius: 'global', categoryIds, @@ -131,7 +131,7 @@ describe('Group', () => { id: 'my-group', name: 'The Best Group', about: 'We will change the world!', - description: 'Some description' + descriptionAddition100, + description: 'Some description' + descriptionAdditional100, groupType: 'public', actionRadius: 'regional', categoryIds, @@ -214,7 +214,7 @@ describe('CreateGroup', () => { name: 'The Best Group', slug: 'the-group', about: 'We will change the world!', - description: 'Some description' + descriptionAddition100, + description: 'Some description' + descriptionAdditional100, groupType: 'public', actionRadius: 'regional', categoryIds,