Close neo4j driver sessions

We had this error in our neo4j pod recently:

```
2019-12-02 08:29:42.680+0000 ERROR Unable to schedule bolt session 'bolt-1018230' for execution since there are no available threads to serve it at the moment. You can retry at a later time or consider increasing max thread pool size for bolt connector(s).
2019-12-02 08:29:42.680+0000 ERROR Unable to schedule bolt session 'bolt-1018224' for execution since there are no available threads to serve it at the moment. You can retry at a later time or consider increasing max thread pool size for bolt connector(s).
2019-12-02 08:29:42.681+0000 ERROR Unable to schedule bolt session 'bolt-1018352' for execution since there are no available threads to serve it at the moment. You can retry at a later time or consider increasing max thread pool size for bolt connector(s).
2019-12-02 08:29:42.682+0000 ERROR Unable to schedule bolt session 'bolt-1018243' for execution since there are no available threads to serve it at the moment. You can retry at a later time or consider increasing max thread pool size for bolt connector(s).
```

Apparently the default is 400 threads. So we must have a leak somewhere.
This commit is contained in:
roschaefer 2019-12-02 18:11:25 +01:00
parent 35c3219460
commit 132c12a7d3
19 changed files with 350 additions and 288 deletions

View File

@ -11,15 +11,21 @@ export default async (driver, authorizationHeader) => {
} catch (err) { } catch (err) {
return null return null
} }
const session = driver.session()
const query = ` const query = `
MATCH (user:User {id: $id, deleted: false, disabled: false }) MATCH (user:User {id: $id, deleted: false, disabled: false })
SET user.lastActiveAt = toString(datetime()) SET user.lastActiveAt = toString(datetime())
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId} RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
LIMIT 1 LIMIT 1
` `
const result = await session.run(query, { id }) const session = driver.session()
let result
try {
result = await session.run(query, { id })
} finally {
session.close() session.close()
}
const [currentUser] = await result.records.map(record => { const [currentUser] = await result.records.map(record => {
return record.get('user') return record.get('user')
}) })

View File

@ -3,7 +3,6 @@ import extractHashtags from '../hashtags/extractHashtags'
const updateHashtagsOfPost = async (postId, hashtags, context) => { const updateHashtagsOfPost = async (postId, hashtags, context) => {
if (!hashtags.length) return if (!hashtags.length) return
const session = context.driver.session()
// We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement // We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement
// functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted // functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted
// and no new Hashtags and relations will be created. // and no new Hashtags and relations will be created.
@ -19,6 +18,8 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => {
MERGE (p)-[:TAGGED]->(t) MERGE (p)-[:TAGGED]->(t)
RETURN p, t RETURN p, t
` `
const session = context.driver.session()
try {
await session.run(cypherDeletePreviousRelations, { await session.run(cypherDeletePreviousRelations, {
postId, postId,
}) })
@ -26,7 +27,9 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => {
postId, postId,
hashtags, hashtags,
}) })
} finally {
session.close() session.close()
}
} }
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {

View File

@ -1,15 +1,19 @@
import extractMentionedUsers from './mentions/extractMentionedUsers' import extractMentionedUsers from './mentions/extractMentionedUsers'
const postAuthorOfComment = async (comment, { context }) => { const postAuthorOfComment = async (comment, { context }) => {
const session = context.driver.session()
const cypherFindUser = ` const cypherFindUser = `
MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId }) MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
RETURN user { .id } RETURN user { .id }
` `
const result = await session.run(cypherFindUser, { const session = context.driver.session()
let result
try {
result = await session.run(cypherFindUser, {
commentId: comment.id, commentId: comment.id,
}) })
} finally {
session.close() session.close()
}
const [postAuthor] = await result.records.map(record => { const [postAuthor] = await result.records.map(record => {
return record.get('user') return record.get('user')
}) })
@ -31,7 +35,6 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
throw new Error('Notification does not fit the reason!') throw new Error('Notification does not fit the reason!')
} }
const session = context.driver.session()
let cypher let cypher
switch (reason) { switch (reason) {
case 'mentioned_in_post': { case 'mentioned_in_post': {
@ -85,12 +88,16 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
break break
} }
} }
const session = context.driver.session()
try {
await session.run(cypher, { await session.run(cypher, {
id, id,
idsOfUsers, idsOfUsers,
reason, reason,
}) })
} finally {
session.close() session.close()
}
} }
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
@ -123,15 +130,19 @@ const handleCreateComment = async (resolve, root, args, context, resolveInfo) =>
const comment = await handleContentDataOfComment(resolve, root, args, context, resolveInfo) const comment = await handleContentDataOfComment(resolve, root, args, context, resolveInfo)
if (comment) { if (comment) {
const session = context.driver.session()
const cypherFindUser = ` const cypherFindUser = `
MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId }) MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
RETURN user { .id } RETURN user { .id }
` `
const result = await session.run(cypherFindUser, { const session = context.driver.session()
let result
try {
result = await session.run(cypherFindUser, {
commentId: comment.id, commentId: comment.id,
}) })
} finally {
session.close() session.close()
}
const [postAuthor] = await result.records.map(record => { const [postAuthor] = await result.records.map(record => {
return record.get('user') return record.get('user')
}) })

View File

@ -45,8 +45,8 @@ const isAuthor = rule({
cache: 'no_cache', cache: 'no_cache',
})(async (_parent, args, { user, driver }) => { })(async (_parent, args, { user, driver }) => {
if (!user) return false if (!user) return false
const session = driver.session()
const { id: resourceId } = args const { id: resourceId } = args
const session = driver.session()
try { try {
const result = await session.run( const result = await session.run(
` `

View File

@ -3,11 +3,14 @@ import uniqueSlug from './slugify/uniqueSlug'
const isUniqueFor = (context, type) => { const isUniqueFor = (context, type) => {
return async slug => { return async slug => {
const session = context.driver.session() const session = context.driver.session()
try {
const response = await session.run(`MATCH(p:${type} {slug: $slug }) return p.slug`, { const response = await session.run(`MATCH(p:${type} {slug: $slug }) return p.slug`, {
slug, slug,
}) })
session.close()
return response.records.length === 0 return response.records.length === 0
} finally {
session.close()
}
} }
} }

View File

@ -13,6 +13,7 @@ const validateCommentCreation = async (resolve, root, args, context, info) => {
throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`) throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`)
} }
const session = context.driver.session() const session = context.driver.session()
try {
const postQueryRes = await session.run( const postQueryRes = await session.run(
` `
MATCH (post:Post {id: $postId}) MATCH (post:Post {id: $postId})
@ -21,7 +22,6 @@ const validateCommentCreation = async (resolve, root, args, context, info) => {
postId, postId,
}, },
) )
session.close()
const [post] = postQueryRes.records.map(record => { const [post] = postQueryRes.records.map(record => {
return record.get('post') return record.get('post')
}) })
@ -31,6 +31,9 @@ const validateCommentCreation = async (resolve, root, args, context, info) => {
} else { } else {
return resolve(root, args, context, info) return resolve(root, args, context, info)
} }
} finally {
session.close()
}
} }
const validateUpdateComment = async (resolve, root, args, context, info) => { const validateUpdateComment = async (resolve, root, args, context, info) => {
@ -62,6 +65,7 @@ const validateReport = async (resolve, root, args, context, info) => {
const { user, driver } = context const { user, driver } = context
if (resourceId === user.id) throw new Error('You cannot report yourself!') if (resourceId === user.id) throw new Error('You cannot report yourself!')
const session = driver.session() const session = driver.session()
try {
const reportQueryRes = await session.run( const reportQueryRes = await session.run(
` `
MATCH (:User {id:$submitterId})-[:REPORTED]->(resource {id:$resourceId}) MATCH (:User {id:$submitterId})-[:REPORTED]->(resource {id:$resourceId})
@ -72,7 +76,6 @@ const validateReport = async (resolve, root, args, context, info) => {
submitterId: user.id, submitterId: user.id,
}, },
) )
session.close()
const [existingReportedResource] = reportQueryRes.records.map(record => { const [existingReportedResource] = reportQueryRes.records.map(record => {
return { return {
label: record.get('label'), label: record.get('label'),
@ -81,6 +84,9 @@ const validateReport = async (resolve, root, args, context, info) => {
if (existingReportedResource) throw new Error(`${existingReportedResource.label}`) if (existingReportedResource) throw new Error(`${existingReportedResource.label}`)
return resolve(root, args, context, info) return resolve(root, args, context, info)
} finally {
session.close()
}
} }
export default { export default {

View File

@ -13,6 +13,7 @@ export default {
params.id = params.id || uuid() params.id = params.id || uuid()
const session = context.driver.session() const session = context.driver.session()
try {
const createCommentCypher = ` const createCommentCypher = `
MATCH (post:Post {id: $postId}) MATCH (post:Post {id: $postId})
MATCH (author:User {id: $userId}) MATCH (author:User {id: $userId})
@ -28,14 +29,17 @@ export default {
postId, postId,
params, params,
}) })
session.close()
const [comment] = transactionRes.records.map(record => record.get('comment').properties) const [comment] = transactionRes.records.map(record => record.get('comment').properties)
return comment return comment
} finally {
session.close()
}
}, },
UpdateComment: async (_parent, params, context, _resolveInfo) => { UpdateComment: async (_parent, params, context, _resolveInfo) => {
const session = context.driver.session() const session = context.driver.session()
try {
const updateCommentCypher = ` const updateCommentCypher = `
MATCH (comment:Comment {id: $params.id}) MATCH (comment:Comment {id: $params.id})
SET comment += $params SET comment += $params
@ -43,12 +47,15 @@ export default {
RETURN comment RETURN comment
` `
const transactionRes = await session.run(updateCommentCypher, { params }) const transactionRes = await session.run(updateCommentCypher, { params })
session.close()
const [comment] = transactionRes.records.map(record => record.get('comment').properties) const [comment] = transactionRes.records.map(record => record.get('comment').properties)
return comment return comment
} finally {
session.close()
}
}, },
DeleteComment: async (_parent, args, context, _resolveInfo) => { DeleteComment: async (_parent, args, context, _resolveInfo) => {
const session = context.driver.session() const session = context.driver.session()
try {
const transactionRes = await session.run( const transactionRes = await session.run(
` `
MATCH (comment:Comment {id: $commentId}) MATCH (comment:Comment {id: $commentId})
@ -59,9 +66,11 @@ export default {
`, `,
{ commentId: args.id }, { commentId: args.id },
) )
session.close()
const [comment] = transactionRes.records.map(record => record.get('comment').properties) const [comment] = transactionRes.records.map(record => record.get('comment').properties)
return comment return comment
} finally {
session.close()
}
}, },
}, },
Comment: { Comment: {

View File

@ -2,8 +2,8 @@ export default {
Mutation: { Mutation: {
UpdateDonations: async (_parent, params, context, _resolveInfo) => { UpdateDonations: async (_parent, params, context, _resolveInfo) => {
const { driver } = context const { driver } = context
const session = driver.session()
let donations let donations
const session = driver.session()
const writeTxResultPromise = session.writeTransaction(async txc => { const writeTxResultPromise = session.writeTransaction(async txc => {
const updateDonationsTransactionResponse = await txc.run( const updateDonationsTransactionResponse = await txc.run(
` `

View File

@ -4,7 +4,6 @@ export default async function createPasswordReset(options) {
const { driver, nonce, email, issuedAt = new Date() } = options const { driver, nonce, email, issuedAt = new Date() } = options
const normalizedEmail = normalizeEmail(email) const normalizedEmail = normalizeEmail(email)
const session = driver.session() const session = driver.session()
let response = {}
try { try {
const cypher = ` const cypher = `
MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email}) MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email})
@ -23,9 +22,8 @@ export default async function createPasswordReset(options) {
const { name } = record.get('u').properties const { name } = record.get('u').properties
return { email, nonce, name } return { email, nonce, name }
}) })
response = records[0] || {} return records[0] || {}
} finally { } finally {
session.close() session.close()
} }
return response
} }

View File

@ -12,13 +12,16 @@ export default {
RETURN resource {.id} RETURN resource {.id}
` `
const session = driver.session() const session = driver.session()
try {
const res = await session.run(cypher, { id, userId }) const res = await session.run(cypher, { id, userId })
session.close()
const [resource] = res.records.map(record => { const [resource] = res.records.map(record => {
return record.get('resource') return record.get('resource')
}) })
if (!resource) return null if (!resource) return null
return resource.id return resource.id
} finally {
session.close()
}
}, },
enable: async (object, params, { user, driver }) => { enable: async (object, params, { user, driver }) => {
const { id } = params const { id } = params
@ -29,13 +32,16 @@ export default {
RETURN resource {.id} RETURN resource {.id}
` `
const session = driver.session() const session = driver.session()
try {
const res = await session.run(cypher, { id }) const res = await session.run(cypher, { id })
session.close()
const [resource] = res.records.map(record => { const [resource] = res.records.map(record => {
return record.get('resource') return record.get('resource')
}) })
if (!resource) return null if (!resource) return null
return resource.id return resource.id
} finally {
session.close()
}
}, },
}, },
} }

View File

@ -18,7 +18,7 @@ export default {
notifications: async (_parent, args, context, _resolveInfo) => { notifications: async (_parent, args, context, _resolveInfo) => {
const { user: currentUser } = context const { user: currentUser } = context
const session = context.driver.session() const session = context.driver.session()
let notifications, whereClause, orderByClause let whereClause, orderByClause
switch (args.read) { switch (args.read) {
case true: case true:
@ -42,7 +42,6 @@ export default {
} }
const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : '' const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : ''
const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : '' const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : ''
try {
const cypher = ` const cypher = `
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
${whereClause} ${whereClause}
@ -50,19 +49,18 @@ export default {
${orderByClause} ${orderByClause}
${offset} ${limit} ${offset} ${limit}
` `
try {
const result = await session.run(cypher, { id: currentUser.id }) const result = await session.run(cypher, { id: currentUser.id })
notifications = await result.records.map(transformReturnType) return result.records.map(transformReturnType)
} finally { } finally {
session.close() session.close()
} }
return notifications
}, },
}, },
Mutation: { Mutation: {
markAsRead: async (parent, args, context, resolveInfo) => { markAsRead: async (parent, args, context, resolveInfo) => {
const { user: currentUser } = context const { user: currentUser } = context
const session = context.driver.session() const session = context.driver.session()
let notification
try { try {
const cypher = ` const cypher = `
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id}) MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
@ -71,11 +69,10 @@ export default {
` `
const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id }) const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id })
const notifications = await result.records.map(transformReturnType) const notifications = await result.records.map(transformReturnType)
notification = notifications[0] return notifications[0]
} finally { } finally {
session.close() session.close()
} }
return notification
}, },
}, },
NOTIFIED: { NOTIFIED: {

View File

@ -9,7 +9,6 @@ export default {
return createPasswordReset({ driver, nonce, email }) return createPasswordReset({ driver, nonce, email })
}, },
resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => { resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => {
const session = driver.session()
const stillValid = new Date() const stillValid = new Date()
stillValid.setDate(stillValid.getDate() - 1) stillValid.setDate(stillValid.getDate() - 1)
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10) const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
@ -21,6 +20,8 @@ export default {
SET u.encryptedPassword = $encryptedNewPassword SET u.encryptedPassword = $encryptedNewPassword
RETURN pr RETURN pr
` `
const session = driver.session()
try {
const transactionRes = await session.run(cypher, { const transactionRes = await session.run(cypher, {
stillValid, stillValid,
email, email,
@ -29,8 +30,10 @@ export default {
}) })
const [reset] = transactionRes.records.map(record => record.get('pr')) const [reset] = transactionRes.records.map(record => record.get('pr'))
const response = !!(reset && reset.properties.usedAt) const response = !!(reset && reset.properties.usedAt)
session.close()
return response return response
} finally {
session.close()
}
}, },
}, },
} }

View File

@ -15,10 +15,13 @@ let variables
const getAllPasswordResets = async () => { const getAllPasswordResets = async () => {
const session = driver.session() const session = driver.session()
try {
const transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r') const transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r')
const resets = transactionRes.records.map(record => record.get('r')) const resets = transactionRes.records.map(record => record.get('r'))
session.close()
return resets return resets
} finally {
session.close()
}
} }
beforeEach(() => { beforeEach(() => {

View File

@ -54,37 +54,41 @@ export default {
return neo4jgraphql(object, params, context, resolveInfo) return neo4jgraphql(object, params, context, resolveInfo)
}, },
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => { PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
const session = context.driver.session()
const { postId, data } = params const { postId, data } = params
const session = context.driver.session()
try {
const transactionRes = await session.run( const transactionRes = await session.run(
`MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-() `MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
RETURN COUNT(DISTINCT emoted) as emotionsCount RETURN COUNT(DISTINCT emoted) as emotionsCount
`, `,
{ postId, data }, { postId, data },
) )
session.close()
const [emotionsCount] = transactionRes.records.map(record => { const [emotionsCount] = transactionRes.records.map(record => {
return record.get('emotionsCount').low return record.get('emotionsCount').low
}) })
return emotionsCount return emotionsCount
} finally {
session.close()
}
}, },
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => { PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
const session = context.driver.session()
const { postId } = params const { postId } = params
const session = context.driver.session()
try {
const transactionRes = await session.run( const transactionRes = await session.run(
`MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId}) `MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
RETURN collect(emoted.emotion) as emotion`, RETURN collect(emoted.emotion) as emotion`,
{ userId: context.user.id, postId }, { userId: context.user.id, postId },
) )
session.close()
const [emotions] = transactionRes.records.map(record => { const [emotions] = transactionRes.records.map(record => {
return record.get('emotion') return record.get('emotion')
}) })
return emotions return emotions
} finally {
session.close()
}
}, },
}, },
Mutation: { Mutation: {
@ -93,8 +97,6 @@ export default {
delete params.categoryIds delete params.categoryIds
params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
params.id = params.id || uuid() params.id = params.id || uuid()
let post
const createPostCypher = `CREATE (post:Post {params}) const createPostCypher = `CREATE (post:Post {params})
SET post.createdAt = toString(datetime()) SET post.createdAt = toString(datetime())
SET post.updatedAt = toString(datetime()) SET post.updatedAt = toString(datetime())
@ -113,7 +115,7 @@ export default {
try { try {
const transactionRes = await session.run(createPostCypher, createPostVariables) const transactionRes = await session.run(createPostCypher, createPostVariables)
const posts = transactionRes.records.map(record => record.get('post').properties) const posts = transactionRes.records.map(record => record.get('post').properties)
post = posts[0] return posts[0]
} catch (e) { } catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Post with this slug already exists!') throw new UserInputError('Post with this slug already exists!')
@ -121,20 +123,19 @@ export default {
} finally { } finally {
session.close() session.close()
} }
return post
}, },
UpdatePost: async (_parent, params, context, _resolveInfo) => { UpdatePost: async (_parent, params, context, _resolveInfo) => {
const { categoryIds } = params const { categoryIds } = params
delete params.categoryIds delete params.categoryIds
params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
const session = context.driver.session()
let updatePostCypher = `MATCH (post:Post {id: $params.id}) let updatePostCypher = `MATCH (post:Post {id: $params.id})
SET post += $params SET post += $params
SET post.updatedAt = toString(datetime()) SET post.updatedAt = toString(datetime())
WITH post WITH post
` `
const session = context.driver.session()
try {
if (categoryIds && categoryIds.length) { if (categoryIds && categoryIds.length) {
const cypherDeletePreviousRelations = ` const cypherDeletePreviousRelations = `
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category) MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
@ -159,14 +160,15 @@ export default {
const [post] = transactionRes.records.map(record => { const [post] = transactionRes.records.map(record => {
return record.get('post').properties return record.get('post').properties
}) })
session.close()
return post return post
} finally {
session.close()
}
}, },
DeletePost: async (object, args, context, resolveInfo) => { DeletePost: async (object, args, context, resolveInfo) => {
const session = context.driver.session() const session = context.driver.session()
try {
// we cannot set slug to 'UNAVAILABE' because of unique constraints // we cannot set slug to 'UNAVAILABE' because of unique constraints
const transactionRes = await session.run( const transactionRes = await session.run(
` `
@ -182,21 +184,24 @@ export default {
`, `,
{ postId: args.id }, { postId: args.id },
) )
session.close()
const [post] = transactionRes.records.map(record => record.get('post').properties) const [post] = transactionRes.records.map(record => record.get('post').properties)
return post return post
} finally {
session.close()
}
}, },
AddPostEmotions: async (object, params, context, resolveInfo) => { AddPostEmotions: async (object, params, context, resolveInfo) => {
const session = context.driver.session()
const { to, data } = params const { to, data } = params
const { user } = context const { user } = context
const session = context.driver.session()
try {
const transactionRes = await session.run( const transactionRes = await session.run(
`MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id}) `MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo) MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
RETURN userFrom, postTo, emotedRelation`, RETURN userFrom, postTo, emotedRelation`,
{ user, to, data }, { user, to, data },
) )
session.close()
const [emoted] = transactionRes.records.map(record => { const [emoted] = transactionRes.records.map(record => {
return { return {
from: { ...record.get('userFrom').properties }, from: { ...record.get('userFrom').properties },
@ -205,18 +210,21 @@ export default {
} }
}) })
return emoted return emoted
} finally {
session.close()
}
}, },
RemovePostEmotions: async (object, params, context, resolveInfo) => { RemovePostEmotions: async (object, params, context, resolveInfo) => {
const session = context.driver.session()
const { to, data } = params const { to, data } = params
const { id: from } = context.user const { id: from } = context.user
const session = context.driver.session()
try {
const transactionRes = await session.run( const transactionRes = await session.run(
`MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id}) `MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
DELETE emotedRelation DELETE emotedRelation
RETURN userFrom, postTo`, RETURN userFrom, postTo`,
{ from, to, data }, { from, to, data },
) )
session.close()
const [emoted] = transactionRes.records.map(record => { const [emoted] = transactionRes.records.map(record => {
return { return {
from: { ...record.get('userFrom').properties }, from: { ...record.get('userFrom').properties },
@ -225,6 +233,9 @@ export default {
} }
}) })
return emoted return emoted
} finally {
session.close()
}
}, },
pinPost: async (_parent, params, context, _resolveInfo) => { pinPost: async (_parent, params, context, _resolveInfo) => {
let pinnedPostWithNestedAttributes let pinnedPostWithNestedAttributes
@ -242,6 +253,7 @@ export default {
) )
return deletePreviousRelationsResponse.records.map(record => record.get('post').properties) return deletePreviousRelationsResponse.records.map(record => record.get('post').properties)
}) })
try {
await writeTxResultPromise await writeTxResultPromise
writeTxResultPromise = session.writeTransaction(async transaction => { writeTxResultPromise = session.writeTransaction(async transaction => {
@ -260,7 +272,6 @@ export default {
pinnedAt: record.get('pinnedAt'), pinnedAt: record.get('pinnedAt'),
})) }))
}) })
try {
const [transactionResult] = await writeTxResultPromise const [transactionResult] = await writeTxResultPromise
const { pinnedPost, pinnedAt } = transactionResult const { pinnedPost, pinnedAt } = transactionResult
pinnedPostWithNestedAttributes = { pinnedPostWithNestedAttributes = {

View File

@ -5,6 +5,7 @@ export default {
const { resourceId, reasonCategory, reasonDescription } = params const { resourceId, reasonCategory, reasonDescription } = params
const { driver, user } = context const { driver, user } = context
const session = driver.session() const session = driver.session()
try {
const writeTxResultPromise = session.writeTransaction(async txc => { const writeTxResultPromise = session.writeTransaction(async txc => {
const reportRelationshipTransactionResponse = await txc.run( const reportRelationshipTransactionResponse = await txc.run(
` `
@ -29,7 +30,6 @@ export default {
type: record.get('type'), type: record.get('type'),
})) }))
}) })
try {
const txResult = await writeTxResultPromise const txResult = await writeTxResultPromise
if (!txResult[0]) return null if (!txResult[0]) return null
const { report, submitter, resource, type } = txResult[0] const { report, submitter, resource, type } = txResult[0]
@ -61,7 +61,6 @@ export default {
Query: { Query: {
reports: async (_parent, params, context, _resolveInfo) => { reports: async (_parent, params, context, _resolveInfo) => {
const { driver } = context const { driver } = context
const session = driver.session()
let response let response
let orderByClause let orderByClause
switch (params.orderBy) { switch (params.orderBy) {
@ -74,6 +73,7 @@ export default {
default: default:
orderByClause = '' orderByClause = ''
} }
const session = driver.session()
try { try {
const cypher = ` const cypher = `
MATCH (submitter:User)-[report:REPORTED]->(resource) MATCH (submitter:User)-[report:REPORTED]->(resource)

View File

@ -4,6 +4,7 @@ export default {
const { id, type } = params const { id, type } = params
const session = context.driver.session() const session = context.driver.session()
try {
const transactionRes = await session.run( const transactionRes = await session.run(
`MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId}) `MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
WHERE $type IN labels(node) AND NOT userWritten.id = $userId WHERE $type IN labels(node) AND NOT userWritten.id = $userId
@ -20,15 +21,16 @@ export default {
return record.get('isShouted') return record.get('isShouted')
}) })
session.close()
return isShouted return isShouted
} finally {
session.close()
}
}, },
unshout: async (_object, params, context, _resolveInfo) => { unshout: async (_object, params, context, _resolveInfo) => {
const { id, type } = params const { id, type } = params
const session = context.driver.session() const session = context.driver.session()
try {
const transactionRes = await session.run( const transactionRes = await session.run(
`MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id}) `MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
WHERE $type IN labels(node) WHERE $type IN labels(node)
@ -43,9 +45,10 @@ export default {
const [isShouted] = transactionRes.records.map(record => { const [isShouted] = transactionRes.records.map(record => {
return record.get('isShouted') return record.get('isShouted')
}) })
session.close()
return isShouted return isShouted
} finally {
session.close()
}
}, },
}, },
} }

View File

@ -33,10 +33,10 @@ export default {
* Note: invites count is calculated this way because invitation codes are not in use yet * Note: invites count is calculated this way because invitation codes are not in use yet
*/ */
response.countInvites = response.countEmails - response.countUsers response.countInvites = response.countEmails - response.countUsers
return response
} finally { } finally {
session.close() session.close()
} }
return response
}, },
}, },
} }

View File

@ -24,6 +24,7 @@ export default {
// } // }
email = normalizeEmail(email) email = normalizeEmail(email)
const session = driver.session() const session = driver.session()
try {
const result = await session.run( const result = await session.run(
` `
MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail}) MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail})
@ -31,7 +32,6 @@ export default {
`, `,
{ userEmail: email }, { userEmail: email },
) )
session.close()
const [currentUser] = await result.records.map(record => { const [currentUser] = await result.records.map(record => {
return record.get('user') return record.get('user')
}) })
@ -48,6 +48,9 @@ export default {
} else { } else {
throw new AuthenticationError('Incorrect email address or password.') throw new AuthenticationError('Incorrect email address or password.')
} }
} finally {
session.close()
}
}, },
changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => { changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => {
const currentUser = await instance.find('User', user.id) const currentUser = await instance.find('User', user.id)

View File

@ -27,8 +27,8 @@ const factories = {
export const cleanDatabase = async (options = {}) => { export const cleanDatabase = async (options = {}) => {
const { driver = getDriver() } = options const { driver = getDriver() } = options
const session = driver.session()
const cypher = 'MATCH (n) DETACH DELETE n' const cypher = 'MATCH (n) DETACH DELETE n'
const session = driver.session()
try { try {
return await session.run(cypher) return await session.run(cypher)
} finally { } finally {