fix(backend): fix memory leaks (#9239)

This commit is contained in:
Ulf Gebhardt 2026-02-19 01:00:42 +01:00 committed by GitHub
parent c0a7965d24
commit c3a65a410e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 892 additions and 984 deletions

View File

@ -165,7 +165,7 @@ export default {
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
},

View File

@ -48,7 +48,7 @@ export default {
const [comment] = await writeTxResultPromise
return comment
} finally {
session.close()
await session.close()
}
},
UpdateComment: async (_parent, params, context, _resolveInfo) => {
@ -71,7 +71,7 @@ export default {
const [comment] = await writeTxResultPromise
return comment
} finally {
session.close()
await session.close()
}
},
DeleteComment: async (_parent, args, context, _resolveInfo) => {
@ -95,7 +95,7 @@ export default {
const [comment] = await writeTxResultPromise
return comment
} finally {
session.close()
await session.close()
}
},
},

View File

@ -6,59 +6,51 @@ export default {
Query: {
Donations: async (_parent, _params, context, _resolveInfo) => {
const { driver } = context
let donations
const session = driver.session()
const writeTxResultPromise = session.writeTransaction(async (txc) => {
const donationsTransactionResponse = await txc.run(
`
MATCH (donations:Donations)
WITH donations LIMIT 1
RETURN donations
`,
{},
)
return donationsTransactionResponse.records.map(
(record) => record.get('donations').properties,
)
})
try {
const txResult = await writeTxResultPromise
if (!txResult[0]) return null
donations = txResult[0]
const txResult = await session.readTransaction(async (txc) => {
const donationsTransactionResponse = await txc.run(
`
MATCH (donations:Donations)
WITH donations LIMIT 1
RETURN donations
`,
{},
)
return donationsTransactionResponse.records.map(
(record) => record.get('donations').properties,
)
})
return txResult[0] || null
} finally {
session.close()
await session.close()
}
return donations
},
},
Mutation: {
UpdateDonations: async (_parent, params, context, _resolveInfo) => {
const { driver } = context
let donations
const session = driver.session()
const writeTxResultPromise = session.writeTransaction(async (txc) => {
const updateDonationsTransactionResponse = await txc.run(
`
MATCH (donations:Donations)
WITH donations LIMIT 1
SET donations += $params
SET donations.updatedAt = toString(datetime())
RETURN donations
`,
{ params },
)
return updateDonationsTransactionResponse.records.map(
(record) => record.get('donations').properties,
)
})
try {
const txResult = await writeTxResultPromise
if (!txResult[0]) return null
donations = txResult[0]
const txResult = await session.writeTransaction(async (txc) => {
const updateDonationsTransactionResponse = await txc.run(
`
MATCH (donations:Donations)
WITH donations LIMIT 1
SET donations += $params
SET donations.updatedAt = toString(datetime())
RETURN donations
`,
{ params },
)
return updateDonationsTransactionResponse.records.map(
(record) => record.get('donations').properties,
)
})
return txResult[0] || null
} finally {
session.close()
await session.close()
}
return donations
},
},
}

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
@ -17,27 +16,24 @@ export default {
VerifyNonce: async (_parent, args, context, _resolveInfo) => {
args.email = normalizeEmail(args.email)
const session = context.driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
const result = await txc.run(
`
MATCH (email:EmailAddress {email: $email, nonce: $nonce})
RETURN count(email) > 0 AS result
`,
{ email: args.email, nonce: args.nonce },
)
return result
})
try {
const txResult = await readTxResultPromise
const txResult = await session.readTransaction(async (txc) => {
return await txc.run(
`
MATCH (email:EmailAddress {email: $email, nonce: $nonce})
RETURN count(email) > 0 AS result
`,
{ email: args.email, nonce: args.nonce },
)
})
return txResult.records[0].get('result')
} finally {
session.close()
await session.close()
}
},
},
Mutation: {
AddEmailAddress: async (_parent, args, context, _resolveInfo) => {
let response
args.email = normalizeEmail(args.email)
try {
const { neode } = context
@ -57,65 +53,64 @@ export default {
} = context
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (txc) => {
const result = await txc.run(
`
MATCH (user:User {id: $userId})
MERGE (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
SET email.createdAt = toString(datetime())
RETURN email, user
`,
{ userId, email: args.email, nonce },
)
return result.records.map((record) => ({
name: record.get('user').properties.name,
locale: record.get('user').properties.locale,
...record.get('email').properties,
}))
})
try {
const txResult = await writeTxResultPromise
response = txResult[0]
const txResult = await session.writeTransaction(async (txc) => {
const result = await txc.run(
`
MATCH (user:User {id: $userId})
MERGE (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
SET email.createdAt = toString(datetime())
RETURN email, user
`,
{ userId, email: args.email, nonce },
)
return result.records.map((record) => ({
name: record.get('user').properties.name,
locale: record.get('user').properties.locale,
...record.get('email').properties,
}))
})
const response = txResult[0]
if (!response) throw new UserInputError('User not found.')
return response
} finally {
session.close()
await session.close()
}
return response
},
VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => {
let response
const {
user: { id: userId },
} = context
args.email = normalizeEmail(args.email)
const { nonce, email } = args
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (txc) => {
const result = await txc.run(
`
MATCH (user:User {id: $userId})-[:PRIMARY_EMAIL]->(previous:EmailAddress)
MATCH (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
OPTIONAL MATCH (abandonedEmail:EmailAddress{email: $email}) WHERE NOT EXISTS ((abandonedEmail)<-[]-())
DELETE abandonedEmail
MERGE (user)-[:PRIMARY_EMAIL]->(email)
SET email:EmailAddress
SET email.verifiedAt = toString(datetime())
REMOVE email:UnverifiedEmailAddress
DETACH DELETE previous
RETURN email
`,
{ userId, email, nonce },
)
return result.records.map((record) => record.get('email').properties)
})
let response
try {
const txResult = await writeTxResultPromise
const txResult = await session.writeTransaction(async (txc) => {
const result = await txc.run(
`
MATCH (user:User {id: $userId})-[:PRIMARY_EMAIL]->(previous:EmailAddress)
MATCH (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
OPTIONAL MATCH (abandonedEmail:EmailAddress{email: $email}) WHERE NOT EXISTS ((abandonedEmail)<-[]-())
DELETE abandonedEmail
MERGE (user)-[:PRIMARY_EMAIL]->(email)
SET email:EmailAddress
SET email.verifiedAt = toString(datetime())
REMOVE email:UnverifiedEmailAddress
DETACH DELETE previous
RETURN email
`,
{ userId, email, nonce },
)
return result.records.map((record) => record.get('email').properties)
})
response = txResult[0]
} catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('A user account with this email already exists.')
throw new Error(e)
throw e
} finally {
session.close()
await session.close()
}
if (!response) throw new UserInputError('Invalid nonce or no email address found.')
return response

View File

@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
@ -13,10 +12,7 @@ import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '@constants/groups'
import { removeHtmlTags } from '@middleware/helpers/cleanHtml'
import type { Context } from '@src/context'
import Resolver, {
removeUndefinedNullValuesFromObject,
convertObjectToCypherMapLiteral,
} from './helpers/Resolver'
import Resolver from './helpers/Resolver'
import { images } from './images/images'
import { createOrUpdateLocations } from './users/location'
@ -24,35 +20,40 @@ export default {
Query: {
Group: async (_object, params, context: Context, _resolveInfo) => {
const { isMember, id, slug, first, offset } = params
const matchParams = { id, slug }
removeUndefinedNullValuesFromObject(matchParams)
const session = context.driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
const transactionResponse = await txc.run(
`
MATCH (group:Group${convertObjectToCypherMapLiteral(matchParams, true)})
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
WITH group, membership
${(isMember === true && "WHERE membership IS NOT NULL AND (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
${(isMember === false && "WHERE membership IS NULL AND (group.groupType IN ['public', 'closed'])") || ''}
${(isMember === undefined && "WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
RETURN group {.*, myRole: membership.role}
ORDER BY group.createdAt DESC
${first !== undefined && offset !== undefined ? `SKIP ${offset} LIMIT ${first}` : ''}
`,
{
userId: context.user.id,
},
)
return transactionResponse.records.map((record) => record.get('group'))
})
try {
return await readTxResultPromise
} catch (error) {
throw new Error(error)
return await session.readTransaction(async (txc) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
const matchFilters: string[] = []
if (id !== undefined) matchFilters.push('group.id = $id')
if (slug !== undefined) matchFilters.push('group.slug = $slug')
const matchWhere = matchFilters.length ? `WHERE ${matchFilters.join(' AND ')}` : ''
const transactionResponse = await txc.run(
`
MATCH (group:Group)
${matchWhere}
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
WITH group, membership
${(isMember === true && "WHERE membership IS NOT NULL AND (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
${(isMember === false && "WHERE membership IS NULL AND (group.groupType IN ['public', 'closed'])") || ''}
${(isMember === undefined && "WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
RETURN group {.*, myRole: membership.role}
ORDER BY group.createdAt DESC
${first !== undefined && offset !== undefined ? 'SKIP toInteger($offset) LIMIT toInteger($first)' : ''}
`,
{
userId: context.user.id,
id,
slug,
first,
offset,
},
)
return transactionResponse.records.map((record) => record.get('group'))
})
} finally {
await session.close()
}
@ -60,25 +61,22 @@ export default {
GroupMembers: async (_object, params, context: Context, _resolveInfo) => {
const { id: groupId, first = 25, offset = 0 } = 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 {.*}, membership {.*}
SKIP toInteger($offset) LIMIT toInteger($first)
`
const transactionResponse = await txc.run(groupMemberCypher, {
groupId,
first,
offset,
})
return transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
})
try {
return await readTxResultPromise
} catch (error) {
throw new Error(error)
return await session.readTransaction(async (txc) => {
const groupMemberCypher = `
MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
RETURN user {.*}, membership {.*}
SKIP toInteger($offset) LIMIT toInteger($first)
`
const transactionResponse = await txc.run(groupMemberCypher, {
groupId,
first,
offset,
})
return transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
})
} finally {
await session.close()
}
@ -89,31 +87,29 @@ export default {
user: { id: userId },
} = context
const session = context.driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
let cypher
if (isMember) {
cypher = `MATCH (user:User)-[membership:MEMBER_OF]->(group:Group)
WHERE user.id = $userId
AND membership.role IN ['usual', 'admin', 'owner', 'pending']
RETURN toString(count(group)) AS count`
} else {
cypher = `MATCH (group:Group)
OPTIONAL MATCH (user:User)-[membership:MEMBER_OF]->(group)
WHERE user.id = $userId
WITH group, membership
WHERE group.groupType IN ['public', 'closed']
OR membership.role IN ['usual', 'admin', 'owner']
RETURN toString(count(group)) AS count`
}
const transactionResponse = await txc.run(cypher, { userId })
return transactionResponse.records.map((record) => record.get('count'))
})
try {
return parseInt(await readTxResultPromise)
} catch (error) {
throw new Error(error)
const result = await session.readTransaction(async (txc) => {
let cypher
if (isMember) {
cypher = `MATCH (user:User)-[membership:MEMBER_OF]->(group:Group)
WHERE user.id = $userId
AND membership.role IN ['usual', 'admin', 'owner', 'pending']
RETURN toString(count(group)) AS count`
} else {
cypher = `MATCH (group:Group)
OPTIONAL MATCH (user:User)-[membership:MEMBER_OF]->(group)
WHERE user.id = $userId
WITH group, membership
WHERE group.groupType IN ['public', 'closed']
OR membership.role IN ['usual', 'admin', 'owner']
RETURN toString(count(group)) AS count`
}
const transactionResponse = await txc.run(cypher, { userId })
return transactionResponse.records.map((record) => record.get('count'))[0]
})
return parseInt(result, 10) || 0
} finally {
session.close()
await session.close()
}
},
},
@ -138,52 +134,51 @@ export default {
}
params.id = params.id || uuid()
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
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] = ownerCreateGroupTransactionResponse.records.map((record) =>
record.get('group'),
)
return group
})
try {
const group = await writeTxResultPromise
const group = await session.writeTransaction(async (transaction) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
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] = ownerCreateGroupTransactionResponse.records.map((record) =>
record.get('group'),
)
return group
})
// TODO: put in a middleware, see "UpdateGroup", "UpdateUser"
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
return group
} catch (error) {
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Group with this slug already exists!')
throw new Error(error)
throw error
} finally {
await session.close()
}
@ -211,61 +206,59 @@ export default {
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) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
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)
try {
const group = await session.writeTransaction(async (transaction) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
await transaction.run(
`
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(:Category)
DELETE previousRelations
`,
{ groupId },
)
}
let updateGroupCypher = `
MATCH (group:Group {id: $groupId})
SET group += $params
SET group.updatedAt = toString(datetime())
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] = transactionResponse.records.map((record) => record.get('group'))
if (avatarInput) {
await images(context.config).mergeImage(group, 'AVATAR_IMAGE', avatarInput, {
transaction,
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,
})
}
return group
})
try {
const group = await writeTxResultPromise
const [group] = transactionResponse.records.map((record) => record.get('group'))
if (avatarInput) {
await images(context.config).mergeImage(group, 'AVATAR_IMAGE', avatarInput, {
transaction,
})
}
return group
})
// TODO: put in a middleware, see "CreateGroup", "UpdateUser"
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
return group
} catch (error) {
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Group with this slug already exists!')
throw new Error(error)
throw error
} finally {
await session.close()
}
@ -273,29 +266,30 @@ export default {
JoinGroup: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId, userId } = params
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const joinGroupCypher = `
MATCH (user:User {id: $userId}), (group:Group {id: $groupId})
MERGE (user)-[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 user {.*}, membership {.*}
`
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
return transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
})
try {
return (await writeTxResultPromise)[0]
} catch (error) {
throw new Error(error)
const result = await session.writeTransaction(async (transaction) => {
const joinGroupCypher = `
MATCH (user:User {id: $userId}), (group:Group {id: $groupId})
MERGE (user)-[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 user {.*}, membership {.*}
`
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
return transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
})
if (!result[0]) {
throw new UserInputError('Could not find User or Group')
}
return result[0]
} finally {
await session.close()
}
@ -305,8 +299,6 @@ export default {
const session = context.driver.session()
try {
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
} catch (error) {
throw new Error(error)
} finally {
await session.close()
}
@ -314,49 +306,46 @@ export default {
ChangeGroupMemberRole: async (_parent, params, context: Context, _resolveInfo) => {
const { groupId, userId, roleInGroup } = params
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
let postRestrictionCypher = ''
if (['usual', 'admin', 'owner'].includes(roleInGroup)) {
postRestrictionCypher = `
WITH group, member, membership
FOREACH (restriction IN [(member)-[r:CANNOT_SEE]->(:Post)-[:IN]->(group) | r] |
DELETE restriction)`
} else {
postRestrictionCypher = `
WITH group, member, membership
FOREACH (post IN [(p:Post)-[:IN]->(group) | p] |
MERGE (member)-[:CANNOT_SEE]->(post))`
}
const joinGroupCypher = `
MATCH (member:User {id: $userId})
MATCH (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
${postRestrictionCypher}
RETURN member {.*} as user, membership {.*}
`
const transactionResponse = await transaction.run(joinGroupCypher, {
groupId,
userId,
roleInGroup,
})
const [member] = transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
return member
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
let postRestrictionCypher = ''
if (['usual', 'admin', 'owner'].includes(roleInGroup)) {
postRestrictionCypher = `
WITH group, member, membership
FOREACH (restriction IN [(member)-[r:CANNOT_SEE]->(:Post)-[:IN]->(group) | r] |
DELETE restriction)`
} else {
postRestrictionCypher = `
With group, member, membership
FOREACH (post IN [(p:Post)-[:IN]->(group) | p] |
MERGE (member)-[:CANNOT_SEE]->(post))`
}
const joinGroupCypher = `
MATCH (member:User {id: $userId})
MATCH (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
${postRestrictionCypher}
RETURN member {.*} as user, membership {.*}
`
const transactionResponse = await transaction.run(joinGroupCypher, {
groupId,
userId,
roleInGroup,
})
const [member] = transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
return member
})
} finally {
await session.close()
}
@ -366,8 +355,6 @@ export default {
const session = context.driver.session()
try {
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
} catch (error) {
throw new Error(error)
} finally {
await session.close()
}
@ -379,30 +366,24 @@ export default {
const { groupId } = params
const userId = context.user.id
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
const transactionResponse = await transaction.run(
`
MATCH (group:Group { id: $groupId })
MATCH (user:User { id: $userId })
MERGE (user)-[m:MUTED]->(group)
SET m.createdAt = toString(datetime())
RETURN group { .* }
`,
{
groupId,
userId,
},
)
const [group] = transactionResponse.records.map((record) => record.get('group'))
return group
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const transactionResponse = await transaction.run(
`
MATCH (group:Group { id: $groupId })
MATCH (user:User { id: $userId })
MERGE (user)-[m:MUTED]->(group)
SET m.createdAt = toString(datetime())
RETURN group { .* }
`,
{
groupId,
userId,
},
)
const [group] = transactionResponse.records.map((record) => record.get('group'))
return group
})
} finally {
await session.close()
}
@ -414,27 +395,24 @@ export default {
const { groupId } = params
const userId = context.user.id
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const transactionResponse = await transaction.run(
`
MATCH (group:Group { id: $groupId })
MATCH (user:User { id: $userId })
OPTIONAL MATCH (user)-[m:MUTED]->(group)
DELETE m
RETURN group { .* }
`,
{
groupId,
userId,
},
)
const [group] = transactionResponse.records.map((record) => record.get('group'))
return group
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const transactionResponse = await transaction.run(
`
MATCH (group:Group { id: $groupId })
MATCH (user:User { id: $userId })
OPTIONAL MATCH (user)-[m:MUTED]->(group)
DELETE m
RETURN group { .* }
`,
{
groupId,
userId,
},
)
const [group] = transactionResponse.records.map((record) => record.get('group'))
return group
})
} finally {
await session.close()
}
@ -540,9 +518,12 @@ const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId)
groupId,
userId,
})
const [user] = await transactionResponse.records.map((record) => {
const [result] = transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
return user
if (!result) {
throw new UserInputError('User is not a member of this group')
}
return result
})
}

View File

@ -33,20 +33,19 @@ export default function Resolver(type, options: any = {}) {
if (typeof parent[key] !== 'undefined') return parent[key]
const id = parent[idAttribute]
const session = driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
const cypher = `
MATCH(:${type} {${idAttribute}: $id})${connection}
RETURN related {.*} as related
`
const result = await txc.run(cypher, { id, cypherParams })
return result.records.map((r) => r.get('related'))
})
try {
let response = await readTxResultPromise
let response = await session.readTransaction(async (txc) => {
const cypher = `
MATCH(:${type} {${idAttribute}: $id})${connection}
RETURN related {.*} as related
`
const result = await txc.run(cypher, { id, cypherParams })
return result.records.map((r) => r.get('related'))
})
if (returnType === 'object') response = response[0] || null
return response
} finally {
session.close()
await session.close()
}
}
}
@ -59,17 +58,16 @@ export default function Resolver(type, options: any = {}) {
if (typeof parent[key] !== 'undefined') return parent[key]
const id = parent[idAttribute]
const session = driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
const nodeCondition = condition.replace('this', 'this {id: $id}')
const cypher = `${nodeCondition} as ${key}`
const result = await txc.run(cypher, { id, cypherParams })
const [response] = result.records.map((r) => r.get(key))
return response
})
try {
return await readTxResultPromise
return await session.readTransaction(async (txc) => {
const nodeCondition = condition.replace('this', 'this {id: $id}')
const cypher = `${nodeCondition} as ${key}`
const result = await txc.run(cypher, { id, cypherParams })
const [response] = result.records.map((r) => r.get(key))
return response
})
} finally {
session.close()
await session.close()
}
}
}
@ -82,20 +80,19 @@ export default function Resolver(type, options: any = {}) {
resolvers[key] = async (parent, _params, { driver, cypherParams }, _resolveInfo) => {
if (typeof parent[key] !== 'undefined') return parent[key]
const session = driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => {
const id = parent[idAttribute]
const cypher = `
MATCH(u:${type} {${idAttribute}: $id})${connection}
RETURN COUNT(DISTINCT(related)) as count
`
const result = await txc.run(cypher, { id, cypherParams })
const [response] = result.records.map((r) => r.get('count').toNumber())
return response
})
try {
return await readTxResultPromise
return await session.readTransaction(async (txc) => {
const id = parent[idAttribute]
const cypher = `
MATCH(u:${type} {${idAttribute}: $id})${connection}
RETURN COUNT(DISTINCT(related)) as count
`
const result = await txc.run(cypher, { id, cypherParams })
const [response] = result.records.map((r) => r.get('count').toNumber())
return response
})
} finally {
session.close()
await session.close()
}
}
}

View File

@ -8,7 +8,7 @@ export default async function alreadyExistingMail({ args, context }) {
args.email = normalizeEmail(args.email)
const session = context.driver.session()
try {
const existingEmailAddressTxPromise = session.writeTransaction(async (transaction) => {
const result = await session.readTransaction(async (transaction) => {
const existingEmailAddressTransactionResponse = await transaction.run(
`
MATCH (email:EmailAddress {email: $email})
@ -24,13 +24,13 @@ export default async function alreadyExistingMail({ args, context }) {
}
})
})
const [emailBelongsToUser] = await existingEmailAddressTxPromise
const [emailBelongsToUser] = result
/*
const { alreadyExistingEmail, user } =
if (user) throw new UserInputError('A user account with this email already exists.')
*/
return emailBelongsToUser || {}
} finally {
session.close()
await session.close()
}
}

View File

@ -6,29 +6,29 @@ import { mergeWith, isArray } from 'lodash'
const getInvisiblePosts = async (context) => {
const session = context.driver.session()
const readTxResultPromise = await session.readTransaction(async (transaction) => {
let cypher = ''
const { user } = context
if (user?.id) {
cypher = `
MATCH (post:Post)<-[:CANNOT_SEE]-(user:User { id: $userId })
RETURN collect(post.id) AS invisiblePostIds`
} else {
cypher = `
MATCH (post:Post)-[:IN]->(group:Group)
WHERE NOT group.groupType = 'public'
RETURN collect(post.id) AS invisiblePostIds`
}
const invisiblePostIdsResponse = await transaction.run(cypher, {
userId: user ? user.id : null,
})
return invisiblePostIdsResponse.records.map((record) => record.get('invisiblePostIds'))
})
try {
const [invisiblePostIds] = readTxResultPromise
const readTxResult = await session.readTransaction(async (transaction) => {
let cypher = ''
const { user } = context
if (user?.id) {
cypher = `
MATCH (post:Post)<-[:CANNOT_SEE]-(user:User { id: $userId })
RETURN collect(post.id) AS invisiblePostIds`
} else {
cypher = `
MATCH (post:Post)-[:IN]->(group:Group)
WHERE NOT group.groupType = 'public'
RETURN collect(post.id) AS invisiblePostIds`
}
const invisiblePostIdsResponse = await transaction.run(cypher, {
userId: user ? user.id : null,
})
return invisiblePostIdsResponse.records.map((record) => record.get('invisiblePostIds'))
})
const [invisiblePostIds] = readTxResult
return invisiblePostIds
} finally {
session.close()
await session.close()
}
}

View File

@ -9,19 +9,19 @@ const getMyGroupIds = async (context) => {
if (!user?.id) return []
const session = context.driver.session()
const readTxResultPromise = await session.readTransaction(async (transaction) => {
const cypher = `
MATCH (group:Group)<-[membership:MEMBER_OF]-(:User { id: $userId })
WHERE membership.role IN ['usual', 'admin', 'owner']
RETURN collect(group.id) AS myGroupIds`
const getMyGroupIdsResponse = await transaction.run(cypher, { userId: user.id })
return getMyGroupIdsResponse.records.map((record) => record.get('myGroupIds'))
})
try {
const [myGroupIds] = readTxResultPromise
const readTxResult = await session.readTransaction(async (transaction) => {
const cypher = `
MATCH (group:Group)<-[membership:MEMBER_OF]-(:User { id: $userId })
WHERE membership.role IN ['usual', 'admin', 'owner']
RETURN collect(group.id) AS myGroupIds`
const getMyGroupIdsResponse = await transaction.run(cypher, { userId: user.id })
return getMyGroupIdsResponse.records.map((record) => record.get('myGroupIds'))
})
const [myGroupIds] = readTxResult
return myGroupIds
} finally {
session.close()
await session.close()
}
}

View File

@ -65,7 +65,7 @@ export default {
await setMessagesAsDistributed(undistributedMessagesIds, session)
}
} finally {
session.close()
await session.close()
}
// send subscription to author to updated the messages
}
@ -82,43 +82,41 @@ export default {
const session = context.driver.session()
try {
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
return await session.writeTransaction(async (transaction) => {
const createMessageCypher = `
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image)
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)
OPTIONAL MATCH (room)<-[:CHATS_IN]-(recipientUser:User)
WHERE NOT recipientUser.id = $currentUserId
WITH MAX(m.indexId) as maxIndex, room, currentUser, image, recipientUser
CREATE (currentUser)-[:CREATED]->(message:Message {
createdAt: toString(datetime()),
id: apoc.create.uuid(),
indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END,
content: LEFT($content,2000),
saved: true,
distributed: false,
seen: false
})-[:INSIDE]->(room)
SET room.lastMessageAt = toString(datetime())
RETURN message {
.*,
indexId: toString(message.indexId),
recipientId: recipientUser.id,
senderId: currentUser.id,
username: currentUser.name,
avatar: image.url,
date: message.createdAt
}
`
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image)
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)
OPTIONAL MATCH (room)<-[:CHATS_IN]-(recipientUser:User)
WHERE NOT recipientUser.id = $currentUserId
WITH MAX(m.indexId) as maxIndex, room, currentUser, image, recipientUser
CREATE (currentUser)-[:CREATED]->(message:Message {
createdAt: toString(datetime()),
id: apoc.create.uuid(),
indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END,
content: LEFT($content,2000),
saved: true,
distributed: false,
seen: false
})-[:INSIDE]->(room)
SET room.lastMessageAt = toString(datetime())
RETURN message {
.*,
indexId: toString(message.indexId),
recipientId: recipientUser.id,
senderId: currentUser.id,
username: currentUser.name,
avatar: image.url,
date: message.createdAt
}
`
const createMessageTxResponse = await transaction.run(createMessageCypher, {
currentUserId,
roomId,
content,
})
const [message] = await createMessageTxResponse.records.map((record) =>
record.get('message'),
)
const [message] = createMessageTxResponse.records.map((record) => record.get('message'))
// this is the case if the room doesn't exist - requires refactoring for implicit rooms
if (!message) {
@ -142,38 +140,32 @@ export default {
return { ...message, files: atns }
})
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
MarkMessagesAsSeen: async (_parent, params, context, _resolveInfo) => {
const { messageIds } = params
const currentUserId = context.user.id
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const setSeenCypher = `
MATCH (m:Message)<-[:CREATED]-(user:User)
WHERE m.id IN $messageIds AND NOT user.id = $currentUserId
SET m.seen = true
RETURN m { .* }
`
const setSeenTxResponse = await transaction.run(setSeenCypher, {
messageIds,
currentUserId,
})
const messages = await setSeenTxResponse.records.map((record) => record.get('m'))
return messages
})
try {
await writeTxResultPromise
await session.writeTransaction(async (transaction) => {
const setSeenCypher = `
MATCH (m:Message)<-[:CREATED]-(user:User)
WHERE m.id IN $messageIds AND NOT user.id = $currentUserId
SET m.seen = true
RETURN m { .* }
`
const setSeenTxResponse = await transaction.run(setSeenCypher, {
messageIds,
currentUserId,
})
return setSeenTxResponse.records.map((record) => record.get('m'))
})
// send subscription to author to updated the messages
return true
} finally {
session.close()
await session.close()
}
},
},

View File

@ -34,7 +34,7 @@ export default {
const [reviewed] = await reviewWriteTxResultPromise
return reviewed || null
} finally {
session.close()
await session.close()
}
},
},

View File

@ -80,7 +80,7 @@ export default {
const notifications = await readTxResultPromise
return notifications
} finally {
session.close()
await session.close()
}
},
},
@ -111,7 +111,7 @@ export default {
const [notifications] = await writeTxResultPromise
return notifications
} finally {
session.close()
await session.close()
}
},
markAllAsRead: async (parent, args, context, _resolveInfo) => {
@ -140,7 +140,7 @@ export default {
const notifications = await writeTxResultPromise
return notifications
} finally {
session.close()
await session.close()
}
},
},

View File

@ -54,7 +54,7 @@ export default {
const [reset] = await passwordResetTxPromise
return !!reset?.properties.usedAt
} finally {
session.close()
await session.close()
}
},
},

View File

@ -88,7 +88,7 @@ export default {
const [emotionsCount] = await readTxResultPromise
return emotionsCount
} finally {
session.close()
await session.close()
}
},
PostsEmotionsByCurrentUser: async (_object, params, context: Context, _resolveInfo) => {
@ -364,7 +364,7 @@ export default {
const [emoted] = await writeTxResultPromise
return emoted
} finally {
session.close()
await session.close()
}
},
pinPost: async (_parent, params, context: Context, _resolveInfo) => {
@ -464,7 +464,7 @@ export default {
try {
;[unpinnedPost] = await writeTxResultPromise
} finally {
session.close()
await session.close()
}
return unpinnedPost
},
@ -552,7 +552,7 @@ export default {
post.viewedTeaserCount = post.viewedTeaserCount.low
return post
} finally {
session.close()
await session.close()
}
},
toggleObservePost: async (_parent, params, context, _resolveInfo) => {
@ -581,7 +581,7 @@ export default {
post.viewedTeaserCount = post.viewedTeaserCount.low
return post
} finally {
session.close()
await session.close()
}
},
pushPost: async (_parent, params, context: Context, _resolveInfo) => {
@ -705,7 +705,7 @@ export default {
const relatedContributions = await writeTxResultPromise
return relatedContributions
} finally {
session.close()
await session.close()
}
},
},

View File

@ -37,7 +37,7 @@ export default {
const [filedReport] = await fileReportWriteTxResultPromise
return filedReport || null
} finally {
session.close()
await session.close()
}
},
},
@ -108,7 +108,7 @@ export default {
const reports = await reportsReadTxPromise
return reports || []
} finally {
session.close()
await session.close()
}
},
},
@ -143,7 +143,7 @@ export default {
return relationshipWithNestedAttributes
})
} finally {
session.close()
await session.close()
}
return filed
}, */
@ -177,7 +177,7 @@ export default {
return relationshipWithNestedAttributes
})
} finally {
session.close()
await session.close()
}
return reviewed
},

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
@ -53,7 +52,7 @@ export default {
const count = await getUnreadRoomsCount(currentUserId, session)
return count
} finally {
session.close()
await session.close()
}
},
},
@ -67,43 +66,40 @@ export default {
throw new Error('Cannot create a room with self')
}
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const createRoomCypher = `
MATCH (currentUser:User { id: $currentUserId })
MATCH (user:User { id: $userId })
MERGE (currentUser)-[:CHATS_IN]->(room:Room)<-[:CHATS_IN]-(user)
ON CREATE SET
room.createdAt = toString(datetime()),
room.id = apoc.create.uuid()
WITH room, user, currentUser
OPTIONAL MATCH (room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User)
WHERE NOT sender.id = $currentUserId AND NOT message.seen
WITH room, user, currentUser, message,
user.name AS roomName
RETURN room {
.*,
users: [properties(currentUser), properties(user)],
roomName: roomName,
unreadCount: toString(COUNT(DISTINCT message))
}
`
const createRommTxResponse = await transaction.run(createRoomCypher, {
userId,
currentUserId,
})
const [room] = await createRommTxResponse.records.map((record) => record.get('room'))
return room
})
try {
const room = await writeTxResultPromise
const room = await session.writeTransaction(async (transaction) => {
const createRoomCypher = `
MATCH (currentUser:User { id: $currentUserId })
MATCH (user:User { id: $userId })
MERGE (currentUser)-[:CHATS_IN]->(room:Room)<-[:CHATS_IN]-(user)
ON CREATE SET
room.createdAt = toString(datetime()),
room.id = apoc.create.uuid()
WITH room, user, currentUser
OPTIONAL MATCH (room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User)
WHERE NOT sender.id = $currentUserId AND NOT message.seen
WITH room, user, currentUser, message,
user.name AS roomName
RETURN room {
.*,
users: [properties(currentUser), properties(user)],
roomName: roomName,
unreadCount: toString(COUNT(DISTINCT message))
}
`
const createRoomTxResponse = await transaction.run(createRoomCypher, {
userId,
currentUserId,
})
const [room] = createRoomTxResponse.records.map((record) => record.get('room'))
return room
})
if (room) {
room.roomId = room.id
}
return room
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
},

View File

@ -134,7 +134,7 @@ const getSearchResults = async (context, setup, params, resultCallback = searchR
const results = await searchResultPromise(session, setup, params)
return resultCallback(results)
} finally {
session.close()
await session.close()
}
}

View File

@ -28,7 +28,7 @@ export default {
const [isShouted] = await shoutWriteTxResultPromise
return isShouted
} finally {
session.close()
await session.close()
}
},
@ -55,7 +55,7 @@ export default {
const [isShouted] = await unshoutWriteTxResultPromise
return isShouted
} finally {
session.close()
await session.close()
}
},
},

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
@ -39,21 +38,16 @@ export default {
})
} AS result`
const session = context.driver.session()
const resultPromise = session.readTransaction(async (transaction) => {
const transactionResponse = transaction.run(cypher, {
id,
})
return transactionResponse
})
try {
const result = await resultPromise
const result = await session.readTransaction(async (transaction) => {
return await transaction.run(cypher, { id })
})
const userData = result.records[0].get('result')
userData.posts.sort(byCreationDate)
userData.posts.forEach((post) => post.comments.sort(byCreationDate))
return userData
} finally {
session.close()
await session.close()
}
},
},

View File

@ -1066,7 +1066,7 @@ describe('setTrophyBadgeSelected', () => {
expect.objectContaining({
errors: [
expect.objectContaining({
message: 'Error: You cannot set badges not rewarded to you.',
message: 'You cannot set badges not rewarded to you.',
}),
],
}),
@ -1083,7 +1083,7 @@ describe('setTrophyBadgeSelected', () => {
expect.objectContaining({
errors: [
expect.objectContaining({
message: 'Error: You cannot set badges not rewarded to you.',
message: 'You cannot set badges not rewarded to you.',
}),
],
}),

View File

@ -49,21 +49,19 @@ export default {
User: async (object, args, context, resolveInfo) => {
if (args.email) {
args.email = normalizeEmail(args.email)
let session
const session = context.driver.session()
try {
session = context.driver.session()
const readTxResult = await session.readTransaction((txc) => {
const result = txc.run(
return txc.run(
`
MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $args.email})
RETURN user {.*, email: e.email}`,
MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $args.email})
RETURN user {.*, email: e.email}`,
{ args },
)
return result
})
return readTxResult.records.map((r) => r.get('user'))
} finally {
session.close()
await session.close()
}
}
return neo4jgraphql(object, args, context, resolveInfo)
@ -105,28 +103,27 @@ export default {
if (currentUser.id === args.id) return null
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const unBlockUserTransactionResponse = await transaction.run(
`
MATCH (blockedUser:User {id: $args.id})
MATCH (currentUser:User {id: $currentUser.id})
OPTIONAL MATCH (currentUser)-[r:FOLLOWS]->(blockedUser)
DELETE r
CREATE (currentUser)-[:BLOCKED]->(blockedUser)
RETURN blockedUser {.*}
`,
{ currentUser, args },
)
return unBlockUserTransactionResponse.records.map((record) => record.get('blockedUser'))[0]
})
try {
const blockedUser = await writeTxResultPromise
const blockedUser = await session.writeTransaction(async (transaction) => {
const blockUserResponse = await transaction.run(
`
MATCH (blockedUser:User {id: $args.id})
MATCH (currentUser:User {id: $currentUser.id})
OPTIONAL MATCH (currentUser)-[r:FOLLOWS]->(blockedUser)
DELETE r
MERGE (currentUser)-[:BLOCKED]->(blockedUser)
RETURN blockedUser {.*}
`,
{ currentUser, args },
)
return blockUserResponse.records.map((record) => record.get('blockedUser'))[0]
})
if (!blockedUser) {
throw new UserInputError('Could not find User')
}
return blockedUser
} finally {
session.close()
await session.close()
}
},
unblockUser: async (_object, args, context, _resolveInfo) => {
@ -134,25 +131,22 @@ export default {
if (currentUser.id === args.id) return null
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const unBlockUserTransactionResponse = await transaction.run(
`
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(blockedUser:User {id: $args.id})
DELETE r
RETURN blockedUser {.*}
`,
{ currentUser, args },
)
return unBlockUserTransactionResponse.records.map((record) => record.get('blockedUser'))[0]
})
try {
const unblockedUser = await writeTxResultPromise
const unblockedUser = await session.writeTransaction(async (transaction) => {
const unblockUserResponse = await transaction.run(
`
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(blockedUser:User {id: $args.id})
DELETE r
RETURN blockedUser {.*}
`,
{ currentUser, args },
)
return unblockUserResponse.records.map((record) => record.get('blockedUser'))[0]
})
if (!unblockedUser) {
throw new Error('Could not find blocked User')
throw new UserInputError('Could not find blocked User')
}
return unblockedUser
} catch {
throw new UserInputError('Could not find blocked User')
} finally {
await session.close()
}
@ -183,103 +177,100 @@ export default {
}
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const updateUserTransactionResponse = await transaction.run(
`
MATCH (user:User {id: $params.id})
SET user += $params
SET user.updatedAt = toString(datetime())
RETURN user {.*}
`,
{ params },
)
const [user] = updateUserTransactionResponse.records.map((record) => record.get('user'))
if (avatarInput) {
await images(context.config).mergeImage(user, 'AVATAR_IMAGE', avatarInput, {
transaction,
})
}
return user
})
try {
const user = await writeTxResultPromise
const user = await session.writeTransaction(async (transaction) => {
const updateUserTransactionResponse = await transaction.run(
`
MATCH (user:User {id: $params.id})
SET user += $params
SET user.updatedAt = toString(datetime())
RETURN user {.*}
`,
{ params },
)
const [user] = updateUserTransactionResponse.records.map((record) => record.get('user'))
if (avatarInput) {
await images(context.config).mergeImage(user, 'AVATAR_IMAGE', avatarInput, {
transaction,
})
}
return user
})
// TODO: put in a middleware, see "CreateGroup", "UpdateGroup"
await createOrUpdateLocations('User', params.id, params.locationName, session, context)
return user
} catch (error) {
throw new UserInputError(error.message)
} finally {
await session.close()
}
},
DeleteUser: async (_object, params, context: Context, _resolveInfo) => {
const { resource, id: userId } = params
const allowedLabels = ['Post', 'Comment']
const session = context.driver.session()
const deleteUserTxResultPromise = session.writeTransaction(async (transaction) => {
if (resource?.length) {
await Promise.all(
resource.map(async (node) => {
const txResult = await transaction.run(
`
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
SET resource.deleted = true
SET resource.content = 'UNAVAILABLE'
SET resource.contentExcerpt = 'UNAVAILABLE'
SET resource.language = 'UNAVAILABLE'
SET resource.createdAt = 'UNAVAILABLE'
SET resource.updatedAt = 'UNAVAILABLE'
SET comment.deleted = true
RETURN resource {.*}
`,
{
userId,
},
)
return Promise.all(
txResult.records
.map((record) => record.get('resource'))
.map((resource) =>
images(context.config).deleteImage(resource, 'HERO_IMAGE', { transaction }),
),
)
}),
)
}
const deleteUserTransactionResponse = await transaction.run(
`
MATCH (user:User {id: $userId})
SET user.deleted = true
SET user.name = 'UNAVAILABLE'
SET user.about = 'UNAVAILABLE'
SET user.lastActiveAt = 'UNAVAILABLE'
SET user.createdAt = 'UNAVAILABLE'
SET user.updatedAt = 'UNAVAILABLE'
SET user.termsAndConditionsAgreedVersion = 'UNAVAILABLE'
SET user.encryptedPassword = null
WITH user
OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress)
DETACH DELETE email
WITH user
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
DETACH DELETE socialMedia
WITH user
OPTIONAL MATCH (user)-[follow:FOLLOWS]-(:User)
DELETE follow
RETURN user {.*}
`,
{ userId },
)
const [user] = deleteUserTransactionResponse.records.map((record) => record.get('user'))
await images(context.config).deleteImage(user, 'AVATAR_IMAGE', { transaction })
return user
})
try {
const user = await deleteUserTxResultPromise
return user
return await session.writeTransaction(async (transaction) => {
if (resource?.length) {
await Promise.all(
resource.map(async (node) => {
if (!allowedLabels.includes(node)) {
throw new UserInputError(`Invalid resource type: ${node}`)
}
const txResult = await transaction.run(
`
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
SET resource.deleted = true
SET resource.content = 'UNAVAILABLE'
SET resource.contentExcerpt = 'UNAVAILABLE'
SET resource.language = 'UNAVAILABLE'
SET resource.createdAt = 'UNAVAILABLE'
SET resource.updatedAt = 'UNAVAILABLE'
SET comment.deleted = true
RETURN resource {.*}
`,
{
userId,
},
)
return Promise.all(
txResult.records
.map((record) => record.get('resource'))
.map((resource) =>
images(context.config).deleteImage(resource, 'HERO_IMAGE', { transaction }),
),
)
}),
)
}
const deleteUserTransactionResponse = await transaction.run(
`
MATCH (user:User {id: $userId})
SET user.deleted = true
SET user.name = 'UNAVAILABLE'
SET user.about = 'UNAVAILABLE'
SET user.lastActiveAt = 'UNAVAILABLE'
SET user.createdAt = 'UNAVAILABLE'
SET user.updatedAt = 'UNAVAILABLE'
SET user.termsAndConditionsAgreedVersion = 'UNAVAILABLE'
SET user.encryptedPassword = null
WITH user
OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress)
DETACH DELETE email
WITH user
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
DETACH DELETE socialMedia
WITH user
OPTIONAL MATCH (user)-[follow:FOLLOWS]-(:User)
DELETE follow
RETURN user {.*}
`,
{ userId },
)
const [user] = deleteUserTransactionResponse.records.map((record) => record.get('user'))
await images(context.config).deleteImage(user, 'AVATAR_IMAGE', { transaction })
return user
})
} finally {
await session.close()
}
@ -289,24 +280,26 @@ export default {
if (context.user.id === id) throw new Error('you-cannot-change-your-own-role')
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const switchUserRoleResponse = await transaction.run(
`
MATCH (user:User {id: $id})
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(e:EmailAddress)
SET user.role = $role
SET user.updatedAt = toString(datetime())
RETURN user {.*, email: e.email}
`,
{ id, role },
)
return switchUserRoleResponse.records.map((record) => record.get('user'))[0]
})
try {
const user = await writeTxResultPromise
const user = await session.writeTransaction(async (transaction) => {
const switchUserRoleResponse = await transaction.run(
`
MATCH (user:User {id: $id})
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(e:EmailAddress)
SET user.role = $role
SET user.updatedAt = toString(datetime())
RETURN user {.*, email: e.email}
`,
{ id, role },
)
return switchUserRoleResponse.records.map((record) => record.get('user'))[0]
})
if (!user) {
throw new UserInputError('Could not find User')
}
return user
} finally {
session.close()
await session.close()
}
},
saveCategorySettings: async (_object, args, context, _resolveInfo) => {
@ -316,40 +309,30 @@ export default {
} = context
const session = context.driver.session()
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (user:User { id: $id })-[previousCategories:NOT_INTERESTED_IN]->(category:Category)
DELETE previousCategories
RETURN user, category
`,
{ id },
)
})
// frontend gives [] when all categories are selected (default)
if (activeCategories.length === 0) return true
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const saveCategorySettingsResponse = await transaction.run(
`
MATCH (category:Category) WHERE NOT category.id IN $activeCategories
MATCH (user:User { id: $id })
MERGE (user)-[r:NOT_INTERESTED_IN]->(category)
RETURN user, r, category
`,
{ id, activeCategories },
)
const [user] = await saveCategorySettingsResponse.records.map((record) =>
record.get('user'),
)
return user
})
try {
await writeTxResultPromise
await session.writeTransaction(async (transaction) => {
await transaction.run(
`
MATCH (user:User { id: $id })-[previousCategories:NOT_INTERESTED_IN]->(:Category)
DELETE previousCategories
`,
{ id },
)
// frontend gives [] when all categories are selected (default)
if (activeCategories.length > 0) {
await transaction.run(
`
MATCH (category:Category) WHERE NOT category.id IN $activeCategories
MATCH (user:User { id: $id })
MERGE (user)-[:NOT_INTERESTED_IN]->(category)
`,
{ id, activeCategories },
)
}
})
return true
} finally {
session.close()
await session.close()
}
},
updateOnlineStatus: async (_object, args, context: Context, _resolveInfo) => {
@ -391,41 +374,37 @@ export default {
}
const session = context.driver.session()
const query = session.writeTransaction(async (transaction) => {
const queryBadge = `
MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge {id: $badgeId})
OPTIONAL MATCH (user)-[badgeRelation:SELECTED]->(badge)
OPTIONAL MATCH (user)-[slotRelation:SELECTED{slot: $slot}]->(:Badge)
DELETE badgeRelation, slotRelation
MERGE (user)-[:SELECTED{slot: toInteger($slot)}]->(badge)
RETURN user {.*}
`
const queryEmpty = `
MATCH (user:User {id: $userId})
OPTIONAL MATCH (user)-[slotRelation:SELECTED {slot: $slot}]->(:Badge)
DELETE slotRelation
RETURN user {.*}
`
const isDefault = !badgeId || badgeId === defaultTrophyBadge.id
const result = await transaction.run(isDefault ? queryEmpty : queryBadge, {
userId,
badgeId,
slot,
})
return result.records.map((record) => record.get('user'))[0]
})
try {
const user = await query
const user = await session.writeTransaction(async (transaction) => {
const queryBadge = `
MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge {id: $badgeId})
OPTIONAL MATCH (user)-[badgeRelation:SELECTED]->(badge)
OPTIONAL MATCH (user)-[slotRelation:SELECTED{slot: $slot}]->(:Badge)
DELETE badgeRelation, slotRelation
MERGE (user)-[:SELECTED{slot: toInteger($slot)}]->(badge)
RETURN user {.*}
`
const queryEmpty = `
MATCH (user:User {id: $userId})
OPTIONAL MATCH (user)-[slotRelation:SELECTED {slot: $slot}]->(:Badge)
DELETE slotRelation
RETURN user {.*}
`
const isDefault = !badgeId || badgeId === defaultTrophyBadge.id
const result = await transaction.run(isDefault ? queryEmpty : queryBadge, {
userId,
badgeId,
slot,
})
return result.records.map((record) => record.get('user'))[0]
})
if (!user) {
throw new Error('You cannot set badges not rewarded to you.')
throw new UserInputError('You cannot set badges not rewarded to you.')
}
return user
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
resetTrophyBadgesSelected: async (_object, _args, context, _resolveInfo) => {
@ -434,25 +413,21 @@ export default {
} = context
const session = context.driver.session()
const query = session.writeTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $userId})
OPTIONAL MATCH (user)-[relation:SELECTED]->(:Badge)
DELETE relation
RETURN user {.*}
`,
{ userId },
)
return result.records.map((record) => record.get('user'))[0]
})
try {
return await query
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $userId})
OPTIONAL MATCH (user)-[relation:SELECTED]->(:Badge)
DELETE relation
RETURN user {.*}
`,
{ userId },
)
return result.records.map((record) => record.get('user'))[0]
})
} finally {
session.close()
await session.close()
}
},
},
@ -539,97 +514,80 @@ export default {
},
badgeTrophiesSelected: async (parent, _params, context, _resolveInfo) => {
const session = context.driver.session()
const query = session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})-[relation:SELECTED]->(badge:Badge)
WITH relation, badge
ORDER BY relation.slot ASC
RETURN relation.slot as slot, badge {.*}
`,
{ parent },
)
return result.records
})
try {
const badgesSelected = await query
const badgesSelected = await session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})-[relation:SELECTED]->(badge:Badge)
WITH relation, badge
ORDER BY relation.slot ASC
RETURN relation.slot as slot, badge {.*}
`,
{ parent },
)
return result.records
})
const result = Array(TROPHY_BADGES_SELECTED_MAX).fill(defaultTrophyBadge)
badgesSelected.map((record) => {
badgesSelected.forEach((record) => {
result[record.get('slot')] = record.get('badge')
return true
})
return result
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
badgeTrophiesUnused: async (parent, _params, context, _resolveInfo) => {
const session = context.driver.session()
const query = session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
WHERE NOT (user)-[:SELECTED]-(badge)
RETURN badge {.*}
`,
{ parent },
)
return result.records.map((record) => record.get('badge'))
})
try {
return await query
} catch (error) {
throw new Error(error)
return await session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
WHERE NOT (user)-[:SELECTED]-(badge)
RETURN badge {.*}
`,
{ parent },
)
return result.records.map((record) => record.get('badge'))
})
} finally {
session.close()
await session.close()
}
},
badgeTrophiesUnusedCount: async (parent, _params, context, _resolveInfo) => {
const session = context.driver.session()
const query = session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
WHERE NOT (user)-[:SELECTED]-(badge)
RETURN toString(COUNT(badge)) as count
`,
{ parent },
)
return result.records.map((record) => record.get('count'))[0]
})
try {
return await query
} catch (error) {
throw new Error(error)
return await session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
WHERE NOT (user)-[:SELECTED]-(badge)
RETURN toString(COUNT(badge)) as count
`,
{ parent },
)
return result.records.map((record) => record.get('count'))[0]
})
} finally {
session.close()
await session.close()
}
},
badgeVerification: async (parent, _params, context, _resolveInfo) => {
const session = context.driver.session()
const query = session.writeTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})<-[:VERIFIES]-(verification:Badge)
RETURN verification {.*}
`,
{ parent },
)
return result.records.map((record) => record.get('verification'))[0]
})
try {
const result = await query
const result = await session.readTransaction(async (transaction) => {
const response = await transaction.run(
`
MATCH (user:User {id: $parent.id})<-[:VERIFIES]-(verification:Badge)
RETURN verification {.*}
`,
{ parent },
)
return response.records.map((record) => record.get('verification'))[0]
})
return result ?? defaultVerificationBadge
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
},
...Resolver('User', {

View File

@ -25,7 +25,7 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => {
)
})
} finally {
session.close()
await session.close()
}
}

View File

@ -159,7 +159,7 @@ const postAuthorOfComment = async (commentId, { context }) => {
})
return postAuthorId.records.map((record) => record.get('authorId'))
} finally {
session.close()
await session.close()
}
}
@ -186,21 +186,18 @@ const notifyFollowingUsers = async (postId, groupId, context) => {
}
`
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
postId,
reason,
groupId: groupId || null,
userId: context.user.id,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
postId,
reason,
groupId: groupId || null,
userId: context.user.id,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
} finally {
session.close()
await session.close()
}
}
@ -232,21 +229,18 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
}
`
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
postId,
reason,
groupId,
userId: context.user.id,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
postId,
reason,
groupId,
userId: context.user.id,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
} finally {
session.close()
await session.close()
}
}
@ -267,20 +261,17 @@ const notifyOwnersOfGroup = async (groupId, userId, reason, context) => {
RETURN notification {.*, from: finalGroup, to: properties(owner), email: email, relatedUser: properties(user) }
`
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
groupId,
reason,
userId,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
groupId,
reason,
userId,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
} finally {
session.close()
await session.close()
}
}
@ -304,21 +295,18 @@ const notifyMemberOfGroup = async (groupId, userId, reason, context) => {
RETURN notification {.*, from: finalGroup, to: properties(user), email: email, relatedUser: properties(owner) }
`
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
groupId,
reason,
userId,
ownerId: owner.id,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
groupId,
reason,
userId,
ownerId: owner.id,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
} finally {
session.close()
await session.close()
}
}
@ -376,62 +364,58 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
RETURN notification {.*, from: finalResource, to: properties(user), email: email, relatedUser: properties(user) }
`
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(mentionedCypher, {
id,
idsOfUsers,
reason,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
return await writeTxResultPromise
} catch (error) {
throw new Error(error)
return await session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(mentionedCypher, {
id,
idsOfUsers,
reason,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
} finally {
session.close()
await session.close()
}
}
const notifyUsersOfComment = async (label, commentId, reason, context) => {
await validateNotifyUsers(label, reason)
const session = context.driver.session()
const writeTxResultPromise = await session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(
`
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
WHERE NOT (observingUser)-[:BLOCKED]-(commenter)
AND NOT (observingUser)-[:MUTED]->(commenter)
AND NOT observingUser.id = $userId
OPTIONAL MATCH (observingUser)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
WITH observingUser, emailAddress, post, comment, commenter
MATCH (postAuthor:User)-[:WROTE]->(post)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
SET notification.read = FALSE
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
SET notification.updatedAt = toString(datetime())
WITH notification, observingUser, emailAddress.email as email, post, commenter, postAuthor,
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
RETURN notification {
.*,
from: finalResource,
to: properties(observingUser),
email: email,
relatedUser: properties(commenter)
}
`,
{
commentId,
reason,
userId: context.user.id,
},
)
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
return await writeTxResultPromise
return await session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(
`
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
WHERE NOT (observingUser)-[:BLOCKED]-(commenter)
AND NOT (observingUser)-[:MUTED]->(commenter)
AND NOT observingUser.id = $userId
OPTIONAL MATCH (observingUser)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
WITH observingUser, emailAddress, post, comment, commenter
MATCH (postAuthor:User)-[:WROTE]->(post)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
SET notification.read = FALSE
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
SET notification.updatedAt = toString(datetime())
WITH notification, observingUser, emailAddress.email as email, post, commenter, postAuthor,
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
RETURN notification {
.*,
from: finalResource,
to: properties(observingUser),
email: email,
relatedUser: properties(commenter)
}
`,
{
commentId,
reason,
userId: context.user.id,
},
)
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
} finally {
session.close()
await session.close()
}
}
@ -447,30 +431,29 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
// Find Recipient
const session = context.driver.session()
const messageRecipient = session.readTransaction(async (transaction) => {
const messageRecipientCypher = `
MATCH (senderUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
WHERE NOT recipientUser.id = $currentUserId
AND NOT (recipientUser)-[:BLOCKED]-(senderUser)
AND NOT (recipientUser)-[:MUTED]->(senderUser)
RETURN senderUser {.*}, recipientUser {.*}, emailAddress {.email}
`
const txResponse = await transaction.run(messageRecipientCypher, {
currentUserId,
roomId,
})
return {
senderUser: await txResponse.records.map((record) => record.get('senderUser'))[0],
recipientUser: await txResponse.records.map((record) => record.get('recipientUser'))[0],
email: await txResponse.records.map((record) => record.get('emailAddress'))[0]?.email,
}
})
try {
// Execute Query
const { senderUser, recipientUser, email } = await messageRecipient
const { senderUser, recipientUser, email } = await session.readTransaction(
async (transaction) => {
const messageRecipientCypher = `
MATCH (senderUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
WHERE NOT recipientUser.id = $currentUserId
AND NOT (recipientUser)-[:BLOCKED]-(senderUser)
AND NOT (recipientUser)-[:MUTED]->(senderUser)
RETURN senderUser {.*}, recipientUser {.*}, emailAddress {.email}
`
const txResponse = await transaction.run(messageRecipientCypher, {
currentUserId,
roomId,
})
return {
senderUser: txResponse.records.map((record) => record.get('senderUser'))[0],
recipientUser: txResponse.records.map((record) => record.get('recipientUser'))[0],
email: txResponse.records.map((record) => record.get('emailAddress'))[0]?.email,
}
},
)
if (recipientUser) {
// send subscriptions
@ -493,10 +476,8 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
// Return resolver result to client
return message
} catch (error) {
throw new Error(error)
} finally {
session.close()
await session.close()
}
}

View File

@ -31,7 +31,7 @@ const setPostCounter = async (postId, relation, context) => {
return txc.run(createRelatedCypher(relation), { currentUser, postId })
})
} finally {
session.close()
await session.close()
}
}

View File

@ -38,7 +38,7 @@ const validateCreateComment = async (resolve, root, args, context, info) => {
return resolve(root, args, context, info)
}
} finally {
session.close()
await session.close()
}
}
@ -64,28 +64,27 @@ const validateReview = async (resolve, root, args, context, info) => {
const { user, driver } = context
if (resourceId === user.id) throw new Error('You cannot review yourself!')
const session = driver.session()
const reportReadTxPromise = session.readTransaction(async (transaction) => {
const validateReviewTransactionResponse = await transaction.run(
`
MATCH (resource {id: $resourceId})
WHERE resource:User OR resource:Post OR resource:Comment
OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource)
OPTIONAL MATCH (resource)<-[:WROTE]-(author:User)
RETURN [l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User']][0] AS label, author, filed
`,
{
resourceId,
submitterId: user.id,
},
)
return validateReviewTransactionResponse.records.map((record) => ({
label: record.get('label'),
author: record.get('author'),
filed: record.get('filed'),
}))
})
try {
const txResult = await reportReadTxPromise
const txResult = await session.readTransaction(async (transaction) => {
const validateReviewTransactionResponse = await transaction.run(
`
MATCH (resource {id: $resourceId})
WHERE resource:User OR resource:Post OR resource:Comment
OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource)
OPTIONAL MATCH (resource)<-[:WROTE]-(author:User)
RETURN [l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User']][0] AS label, author, filed
`,
{
resourceId,
submitterId: user.id,
},
)
return validateReviewTransactionResponse.records.map((record) => ({
label: record.get('label'),
author: record.get('author'),
filed: record.get('filed'),
}))
})
existingReportedResource = txResult
if (!existingReportedResource?.length)
throw new Error(`Resource not found or is not a Post|Comment|User!`)
@ -101,7 +100,7 @@ const validateReview = async (resolve, root, args, context, info) => {
if (authorId && authorId === user.id)
throw new Error(`You cannot review your own ${existingReportedResource.label}!`)
} finally {
session.close()
await session.close()
}
return resolve(root, args, context, info)

View File

@ -15,6 +15,7 @@ import helmet from 'helmet'
import CONFIG from './config'
import { context, getContext } from './context'
import schema from './graphql/schema'
import logger from './logger'
import middleware from './middleware'
import type { ApolloServerExpressConfig } from 'apollo-server-express'
@ -24,8 +25,12 @@ const createServer = (options?: ApolloServerExpressConfig) => {
context,
schema: middleware(schema),
subscriptions: {
keepAlive: 10000,
onConnect: (connectionParams) =>
getContext()(connectionParams as { headers: { authorization?: string } }),
onDisconnect: () => {
logger.debug('WebSocket client disconnected')
},
},
debug: !!CONFIG.DEBUG,
uploads: false,

View File

@ -5,11 +5,22 @@ import type { S3Config } from '@config/index'
import { FileUploadCallback, FileDeleteCallback } from './types'
export const s3Service = (config: S3Config, prefix: string) => {
const { AWS_BUCKET: Bucket } = config
let cachedClient: S3Client | null = null
let cachedConfig: S3Config | null = null
const getS3Client = (config: S3Config): S3Client => {
if (cachedClient) {
if (
cachedConfig?.AWS_ENDPOINT !== config.AWS_ENDPOINT ||
cachedConfig?.AWS_ACCESS_KEY_ID !== config.AWS_ACCESS_KEY_ID ||
cachedConfig?.AWS_SECRET_ACCESS_KEY !== config.AWS_SECRET_ACCESS_KEY
) {
throw new Error('S3Client singleton was created with different credentials')
}
return cachedClient
}
const { AWS_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = config
const s3 = new S3Client({
cachedClient = new S3Client({
credentials: {
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
@ -17,6 +28,13 @@ export const s3Service = (config: S3Config, prefix: string) => {
endpoint: AWS_ENDPOINT,
forcePathStyle: true,
})
cachedConfig = config
return cachedClient
}
export const s3Service = (config: S3Config, prefix: string) => {
const { AWS_BUCKET: Bucket } = config
const s3 = getS3Client(config)
const uploadFile: FileUploadCallback = async ({ createReadStream, uniqueFilename, mimetype }) => {
const s3Location = prefix.length > 0 ? `${prefix}/${uniqueFilename}` : uniqueFilename