Wolfgang Huß d87c0271a1
Merge pull request #5317 from Ocelot-Social-Community/5059-groups/5316-in-be-hidden-groups-not-listed-for-none-or-pending-members
feat: 🍰 Hidden Groups Shall Not Be Visible For None Or Pending Members In Backend
2022-09-08 15:42:18 +02:00

286 lines
11 KiB
JavaScript

import { v4 as uuid } from 'uuid'
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'
import Resolver from './helpers/Resolver'
import { mergeImage } from './images/images'
export default {
Query: {
Group: async (_object, params, context, _resolveInfo) => {
const { id: groupId, isMember } = params
const session = context.driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
const groupIdCypher = groupId ? ` {id: "${groupId}"}` : ''
let groupCypher
if (isMember === true) {
groupCypher = `
MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group:Group${groupIdCypher})
WITH group, membership
WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])
RETURN group {.*, myRole: membership.role}
`
} else {
if (isMember === false) {
groupCypher = `
MATCH (group:Group${groupIdCypher})
WHERE (NOT (:User {id: $userId})-[:MEMBER_OF]->(group))
WITH group
WHERE group.groupType IN ['public', 'closed']
RETURN group {.*, myRole: NULL}
`
} else {
groupCypher = `
MATCH (group:Group${groupIdCypher})
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
WITH group, membership
WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])
RETURN group {.*, myRole: membership.role}
`
}
}
const transactionResponse = await txc.run(groupCypher, {
userId: context.user.id,
})
return transactionResponse.records.map((record) => record.get('group'))
})
try {
return await readTxResultPromise
} catch (error) {
throw new Error(error)
} finally {
session.close()
}
},
GroupMembers: async (_object, params, context, _resolveInfo) => {
const { id: groupId } = params
const session = context.driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
const groupMemberCypher = `
MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
RETURN user {.*, myRoleInGroup: membership.role}
`
const transactionResponse = await txc.run(groupMemberCypher, {
groupId,
})
return transactionResponse.records.map((record) => record.get('user'))
})
try {
return await readTxResultPromise
} catch (error) {
throw new Error(error)
} finally {
session.close()
}
},
},
Mutation: {
CreateGroup: async (_parent, params, context, _resolveInfo) => {
const { categoryIds } = params
delete params.categoryIds
if (CONFIG.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) {
throw new UserInputError('Too view categories!')
}
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length > CATEGORIES_MAX) {
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('Description too short!')
}
params.id = params.id || uuid()
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const categoriesCypher =
CONFIG.CATEGORIES_ACTIVE && categoryIds
? `
WITH group, membership
UNWIND $categoryIds AS categoryId
MATCH (category:Category {id: categoryId})
MERGE (group)-[:CATEGORIZED]->(category)
`
: ''
const ownerCreateGroupTransactionResponse = await transaction.run(
`
CREATE (group:Group)
SET group += $params
SET group.createdAt = toString(datetime())
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()),
membership.updatedAt = null,
membership.role = 'owner'
${categoriesCypher}
RETURN group {.*, myRole: membership.role}
`,
{ userId: context.user.id, categoryIds, params },
)
const [group] = await ownerCreateGroupTransactionResponse.records.map((record) =>
record.get('group'),
)
return group
})
try {
return await writeTxResultPromise
} catch (error) {
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Group with this slug already exists!')
throw new Error(error)
} finally {
session.close()
}
},
UpdateGroup: async (_parent, params, context, _resolveInfo) => {
const { categoryIds } = params
const { id: groupId, avatar: avatarInput } = params
delete params.categoryIds
delete params.avatar
if (CONFIG.CATEGORIES_ACTIVE && categoryIds) {
if (categoryIds.length < CATEGORIES_MIN) {
throw new UserInputError('Too view categories!')
}
if (categoryIds.length > CATEGORIES_MAX) {
throw new UserInputError('Too many categories!')
}
}
if (
params.description &&
removeHtmlTags(params.description).length < DESCRIPTION_WITHOUT_HTML_LENGTH_MIN
) {
throw new UserInputError('Description too short!')
}
const session = context.driver.session()
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
const cypherDeletePreviousRelations = `
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category)
DELETE previousRelations
RETURN group, category
`
await session.writeTransaction((transaction) => {
return transaction.run(cypherDeletePreviousRelations, { groupId })
})
}
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
let updateGroupCypher = `
MATCH (group:Group {id: $groupId})
SET group += $params
SET group.updatedAt = toString(datetime())
WITH group
`
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
updateGroupCypher += `
UNWIND $categoryIds AS categoryId
MATCH (category:Category {id: categoryId})
MERGE (group)-[:CATEGORIZED]->(category)
WITH group
`
}
updateGroupCypher += `
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
RETURN group {.*, myRole: membership.role}
`
const transactionResponse = await transaction.run(updateGroupCypher, {
groupId,
userId: context.user.id,
categoryIds,
params,
})
const [group] = await transactionResponse.records.map((record) => record.get('group'))
if (avatarInput) {
await mergeImage(group, 'AVATAR_IMAGE', avatarInput, { transaction })
}
return group
})
try {
return await writeTxResultPromise
} catch (error) {
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Group with this slug already exists!')
throw new Error(error)
} finally {
session.close()
}
},
JoinGroup: async (_parent, params, context, _resolveInfo) => {
const { groupId, userId } = params
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const joinGroupCypher = `
MATCH (member:User {id: $userId}), (group:Group {id: $groupId})
MERGE (member)-[membership:MEMBER_OF]->(group)
ON CREATE SET
membership.createdAt = toString(datetime()),
membership.updatedAt = null,
membership.role =
CASE WHEN group.groupType = 'public'
THEN 'usual'
ELSE 'pending'
END
RETURN member {.*, myRoleInGroup: membership.role}
`
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
const [member] = await transactionResponse.records.map((record) => record.get('member'))
return member
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
} finally {
session.close()
}
},
ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => {
const { groupId, userId, roleInGroup } = params
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const joinGroupCypher = `
MATCH (member:User {id: $userId}), (group:Group {id: $groupId})
MERGE (member)-[membership:MEMBER_OF]->(group)
ON CREATE SET
membership.createdAt = toString(datetime()),
membership.updatedAt = null,
membership.role = $roleInGroup
ON MATCH SET
membership.updatedAt = toString(datetime()),
membership.role = $roleInGroup
RETURN member {.*, myRoleInGroup: membership.role}
`
const transactionResponse = await transaction.run(joinGroupCypher, {
groupId,
userId,
roleInGroup,
})
const [member] = await transactionResponse.records.map((record) => record.get('member'))
return member
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
} finally {
session.close()
}
},
},
Group: {
...Resolver('Group', {
hasMany: {
categories: '-[:CATEGORIZED]->(related:Category)',
},
hasOne: {
avatar: '-[:AVATAR_IMAGE]->(related:Image)',
},
}),
},
}