mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2026-04-06 01:25:31 +00:00
fix(backend): fix memory leaks (#9239)
This commit is contained in:
parent
c0a7965d24
commit
c3a65a410e
@ -165,7 +165,7 @@ export default {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export default {
|
|||||||
const [comment] = await writeTxResultPromise
|
const [comment] = await writeTxResultPromise
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpdateComment: async (_parent, params, context, _resolveInfo) => {
|
UpdateComment: async (_parent, params, context, _resolveInfo) => {
|
||||||
@ -71,7 +71,7 @@ export default {
|
|||||||
const [comment] = await writeTxResultPromise
|
const [comment] = await writeTxResultPromise
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DeleteComment: async (_parent, args, context, _resolveInfo) => {
|
DeleteComment: async (_parent, args, context, _resolveInfo) => {
|
||||||
@ -95,7 +95,7 @@ export default {
|
|||||||
const [comment] = await writeTxResultPromise
|
const [comment] = await writeTxResultPromise
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,59 +6,51 @@ export default {
|
|||||||
Query: {
|
Query: {
|
||||||
Donations: async (_parent, _params, context, _resolveInfo) => {
|
Donations: async (_parent, _params, context, _resolveInfo) => {
|
||||||
const { driver } = context
|
const { driver } = context
|
||||||
let donations
|
|
||||||
const session = driver.session()
|
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 {
|
try {
|
||||||
const txResult = await writeTxResultPromise
|
const txResult = await session.readTransaction(async (txc) => {
|
||||||
if (!txResult[0]) return null
|
const donationsTransactionResponse = await txc.run(
|
||||||
donations = txResult[0]
|
`
|
||||||
|
MATCH (donations:Donations)
|
||||||
|
WITH donations LIMIT 1
|
||||||
|
RETURN donations
|
||||||
|
`,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
return donationsTransactionResponse.records.map(
|
||||||
|
(record) => record.get('donations').properties,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return txResult[0] || null
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
return donations
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
UpdateDonations: async (_parent, params, context, _resolveInfo) => {
|
UpdateDonations: async (_parent, params, context, _resolveInfo) => {
|
||||||
const { driver } = context
|
const { driver } = context
|
||||||
let donations
|
|
||||||
const session = driver.session()
|
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 {
|
try {
|
||||||
const txResult = await writeTxResultPromise
|
const txResult = await session.writeTransaction(async (txc) => {
|
||||||
if (!txResult[0]) return null
|
const updateDonationsTransactionResponse = await txc.run(
|
||||||
donations = txResult[0]
|
`
|
||||||
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
return donations
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -17,27 +16,24 @@ export default {
|
|||||||
VerifyNonce: async (_parent, args, context, _resolveInfo) => {
|
VerifyNonce: async (_parent, args, context, _resolveInfo) => {
|
||||||
args.email = normalizeEmail(args.email)
|
args.email = normalizeEmail(args.email)
|
||||||
const session = context.driver.session()
|
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 {
|
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')
|
return txResult.records[0].get('result')
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
AddEmailAddress: async (_parent, args, context, _resolveInfo) => {
|
AddEmailAddress: async (_parent, args, context, _resolveInfo) => {
|
||||||
let response
|
|
||||||
args.email = normalizeEmail(args.email)
|
args.email = normalizeEmail(args.email)
|
||||||
try {
|
try {
|
||||||
const { neode } = context
|
const { neode } = context
|
||||||
@ -57,65 +53,64 @@ export default {
|
|||||||
} = context
|
} = context
|
||||||
|
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
const txResult = await writeTxResultPromise
|
const txResult = await session.writeTransaction(async (txc) => {
|
||||||
response = txResult[0]
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
return response
|
|
||||||
},
|
},
|
||||||
VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => {
|
VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => {
|
||||||
let response
|
|
||||||
const {
|
const {
|
||||||
user: { id: userId },
|
user: { id: userId },
|
||||||
} = context
|
} = context
|
||||||
args.email = normalizeEmail(args.email)
|
args.email = normalizeEmail(args.email)
|
||||||
const { nonce, email } = args
|
const { nonce, email } = args
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const writeTxResultPromise = session.writeTransaction(async (txc) => {
|
let response
|
||||||
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)
|
|
||||||
})
|
|
||||||
try {
|
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]
|
response = txResult[0]
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||||
throw new UserInputError('A user account with this email already exists.')
|
throw new UserInputError('A user account with this email already exists.')
|
||||||
throw new Error(e)
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
if (!response) throw new UserInputError('Invalid nonce or no email address found.')
|
if (!response) throw new UserInputError('Invalid nonce or no email address found.')
|
||||||
return response
|
return response
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/require-await */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* 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-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* 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 { removeHtmlTags } from '@middleware/helpers/cleanHtml'
|
||||||
import type { Context } from '@src/context'
|
import type { Context } from '@src/context'
|
||||||
|
|
||||||
import Resolver, {
|
import Resolver from './helpers/Resolver'
|
||||||
removeUndefinedNullValuesFromObject,
|
|
||||||
convertObjectToCypherMapLiteral,
|
|
||||||
} from './helpers/Resolver'
|
|
||||||
import { images } from './images/images'
|
import { images } from './images/images'
|
||||||
import { createOrUpdateLocations } from './users/location'
|
import { createOrUpdateLocations } from './users/location'
|
||||||
|
|
||||||
@ -24,35 +20,40 @@ export default {
|
|||||||
Query: {
|
Query: {
|
||||||
Group: async (_object, params, context: Context, _resolveInfo) => {
|
Group: async (_object, params, context: Context, _resolveInfo) => {
|
||||||
const { isMember, id, slug, first, offset } = params
|
const { isMember, id, slug, first, offset } = params
|
||||||
const matchParams = { id, slug }
|
|
||||||
removeUndefinedNullValuesFromObject(matchParams)
|
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await readTxResultPromise
|
return await session.readTransaction(async (txc) => {
|
||||||
} catch (error) {
|
if (!context.user) {
|
||||||
throw new Error(error)
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -60,25 +61,22 @@ export default {
|
|||||||
GroupMembers: async (_object, params, context: Context, _resolveInfo) => {
|
GroupMembers: async (_object, params, context: Context, _resolveInfo) => {
|
||||||
const { id: groupId, first = 25, offset = 0 } = params
|
const { id: groupId, first = 25, offset = 0 } = params
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await readTxResultPromise
|
return await session.readTransaction(async (txc) => {
|
||||||
} catch (error) {
|
const groupMemberCypher = `
|
||||||
throw new Error(error)
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -89,31 +87,29 @@ export default {
|
|||||||
user: { id: userId },
|
user: { id: userId },
|
||||||
} = context
|
} = context
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return parseInt(await readTxResultPromise)
|
const result = await session.readTransaction(async (txc) => {
|
||||||
} catch (error) {
|
let cypher
|
||||||
throw new Error(error)
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -138,52 +134,51 @@ export default {
|
|||||||
}
|
}
|
||||||
params.id = params.id || uuid()
|
params.id = params.id || uuid()
|
||||||
const session = context.driver.session()
|
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 {
|
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"
|
// TODO: put in a middleware, see "UpdateGroup", "UpdateUser"
|
||||||
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
||||||
return group
|
return group
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||||
throw new UserInputError('Group with this slug already exists!')
|
throw new UserInputError('Group with this slug already exists!')
|
||||||
throw new Error(error)
|
throw error
|
||||||
} finally {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -211,61 +206,59 @@ export default {
|
|||||||
throw new UserInputError('Description too short!')
|
throw new UserInputError('Description too short!')
|
||||||
}
|
}
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
try {
|
||||||
const cypherDeletePreviousRelations = `
|
const group = await session.writeTransaction(async (transaction) => {
|
||||||
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category)
|
if (!context.user) {
|
||||||
DELETE previousRelations
|
throw new Error('Missing authenticated user.')
|
||||||
RETURN group, category
|
}
|
||||||
`
|
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||||
await session.writeTransaction((transaction) => {
|
await transaction.run(
|
||||||
return transaction.run(cypherDeletePreviousRelations, { groupId })
|
`
|
||||||
})
|
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(:Category)
|
||||||
}
|
DELETE previousRelations
|
||||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
`,
|
||||||
if (!context.user) {
|
{ groupId },
|
||||||
throw new Error('Missing authenticated user.')
|
)
|
||||||
}
|
}
|
||||||
let updateGroupCypher = `
|
let updateGroupCypher = `
|
||||||
MATCH (group:Group {id: $groupId})
|
MATCH (group:Group {id: $groupId})
|
||||||
SET group += $params
|
SET group += $params
|
||||||
SET group.updatedAt = toString(datetime())
|
SET group.updatedAt = toString(datetime())
|
||||||
WITH group
|
|
||||||
`
|
|
||||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
|
||||||
updateGroupCypher += `
|
|
||||||
UNWIND $categoryIds AS categoryId
|
|
||||||
MATCH (category:Category {id: categoryId})
|
|
||||||
MERGE (group)-[:CATEGORIZED]->(category)
|
|
||||||
WITH group
|
WITH group
|
||||||
`
|
`
|
||||||
}
|
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||||
updateGroupCypher += `
|
updateGroupCypher += `
|
||||||
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
UNWIND $categoryIds AS categoryId
|
||||||
RETURN group {.*, myRole: membership.role}
|
MATCH (category:Category {id: categoryId})
|
||||||
`
|
MERGE (group)-[:CATEGORIZED]->(category)
|
||||||
const transactionResponse = await transaction.run(updateGroupCypher, {
|
WITH group
|
||||||
groupId,
|
`
|
||||||
userId: context.user.id,
|
}
|
||||||
categoryIds,
|
updateGroupCypher += `
|
||||||
params,
|
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
||||||
})
|
RETURN group {.*, myRole: membership.role}
|
||||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
`
|
||||||
if (avatarInput) {
|
const transactionResponse = await transaction.run(updateGroupCypher, {
|
||||||
await images(context.config).mergeImage(group, 'AVATAR_IMAGE', avatarInput, {
|
groupId,
|
||||||
transaction,
|
userId: context.user.id,
|
||||||
|
categoryIds,
|
||||||
|
params,
|
||||||
})
|
})
|
||||||
}
|
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||||
return group
|
if (avatarInput) {
|
||||||
})
|
await images(context.config).mergeImage(group, 'AVATAR_IMAGE', avatarInput, {
|
||||||
try {
|
transaction,
|
||||||
const group = await writeTxResultPromise
|
})
|
||||||
|
}
|
||||||
|
return group
|
||||||
|
})
|
||||||
// TODO: put in a middleware, see "CreateGroup", "UpdateUser"
|
// TODO: put in a middleware, see "CreateGroup", "UpdateUser"
|
||||||
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
||||||
return group
|
return group
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||||
throw new UserInputError('Group with this slug already exists!')
|
throw new UserInputError('Group with this slug already exists!')
|
||||||
throw new Error(error)
|
throw error
|
||||||
} finally {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -273,29 +266,30 @@ export default {
|
|||||||
JoinGroup: async (_parent, params, context: Context, _resolveInfo) => {
|
JoinGroup: async (_parent, params, context: Context, _resolveInfo) => {
|
||||||
const { groupId, userId } = params
|
const { groupId, userId } = params
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return (await writeTxResultPromise)[0]
|
const result = await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const joinGroupCypher = `
|
||||||
throw new Error(error)
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -305,8 +299,6 @@ export default {
|
|||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -314,49 +306,46 @@ export default {
|
|||||||
ChangeGroupMemberRole: async (_parent, params, context: Context, _resolveInfo) => {
|
ChangeGroupMemberRole: async (_parent, params, context: Context, _resolveInfo) => {
|
||||||
const { groupId, userId, roleInGroup } = params
|
const { groupId, userId, roleInGroup } = params
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
let postRestrictionCypher = ''
|
||||||
throw new Error(error)
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -366,8 +355,6 @@ export default {
|
|||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -379,30 +366,24 @@ export default {
|
|||||||
const { groupId } = params
|
const { groupId } = params
|
||||||
const userId = context.user.id
|
const userId = context.user.id
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const transactionResponse = await transaction.run(
|
||||||
throw new Error(error)
|
`
|
||||||
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -414,27 +395,24 @@ export default {
|
|||||||
const { groupId } = params
|
const { groupId } = params
|
||||||
const userId = context.user.id
|
const userId = context.user.id
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const transactionResponse = await transaction.run(
|
||||||
throw new Error(error)
|
`
|
||||||
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -540,9 +518,12 @@ const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId)
|
|||||||
groupId,
|
groupId,
|
||||||
userId,
|
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: record.get('user'), membership: record.get('membership') }
|
||||||
})
|
})
|
||||||
return user
|
if (!result) {
|
||||||
|
throw new UserInputError('User is not a member of this group')
|
||||||
|
}
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,20 +33,19 @@ export default function Resolver(type, options: any = {}) {
|
|||||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||||
const id = parent[idAttribute]
|
const id = parent[idAttribute]
|
||||||
const session = driver.session()
|
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 {
|
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
|
if (returnType === 'object') response = response[0] || null
|
||||||
return response
|
return response
|
||||||
} finally {
|
} 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]
|
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||||
const id = parent[idAttribute]
|
const id = parent[idAttribute]
|
||||||
const session = driver.session()
|
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 {
|
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 {
|
} 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) => {
|
resolvers[key] = async (parent, _params, { driver, cypherParams }, _resolveInfo) => {
|
||||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||||
const session = driver.session()
|
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 {
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export default async function alreadyExistingMail({ args, context }) {
|
|||||||
args.email = normalizeEmail(args.email)
|
args.email = normalizeEmail(args.email)
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const existingEmailAddressTxPromise = session.writeTransaction(async (transaction) => {
|
const result = await session.readTransaction(async (transaction) => {
|
||||||
const existingEmailAddressTransactionResponse = await transaction.run(
|
const existingEmailAddressTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (email:EmailAddress {email: $email})
|
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 } =
|
const { alreadyExistingEmail, user } =
|
||||||
if (user) throw new UserInputError('A user account with this email already exists.')
|
if (user) throw new UserInputError('A user account with this email already exists.')
|
||||||
*/
|
*/
|
||||||
return emailBelongsToUser || {}
|
return emailBelongsToUser || {}
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,29 +6,29 @@ import { mergeWith, isArray } from 'lodash'
|
|||||||
|
|
||||||
const getInvisiblePosts = async (context) => {
|
const getInvisiblePosts = async (context) => {
|
||||||
const session = context.driver.session()
|
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 {
|
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
|
return invisiblePostIds
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,19 +9,19 @@ const getMyGroupIds = async (context) => {
|
|||||||
if (!user?.id) return []
|
if (!user?.id) return []
|
||||||
const session = context.driver.session()
|
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 {
|
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
|
return myGroupIds
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export default {
|
|||||||
await setMessagesAsDistributed(undistributedMessagesIds, session)
|
await setMessagesAsDistributed(undistributedMessagesIds, session)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
// send subscription to author to updated the messages
|
// send subscription to author to updated the messages
|
||||||
}
|
}
|
||||||
@ -82,43 +82,41 @@ export default {
|
|||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
return await session.writeTransaction(async (transaction) => {
|
||||||
const createMessageCypher = `
|
const createMessageCypher = `
|
||||||
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
||||||
OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image)
|
OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image)
|
||||||
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)
|
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)
|
||||||
OPTIONAL MATCH (room)<-[:CHATS_IN]-(recipientUser:User)
|
OPTIONAL MATCH (room)<-[:CHATS_IN]-(recipientUser:User)
|
||||||
WHERE NOT recipientUser.id = $currentUserId
|
WHERE NOT recipientUser.id = $currentUserId
|
||||||
WITH MAX(m.indexId) as maxIndex, room, currentUser, image, recipientUser
|
WITH MAX(m.indexId) as maxIndex, room, currentUser, image, recipientUser
|
||||||
CREATE (currentUser)-[:CREATED]->(message:Message {
|
CREATE (currentUser)-[:CREATED]->(message:Message {
|
||||||
createdAt: toString(datetime()),
|
createdAt: toString(datetime()),
|
||||||
id: apoc.create.uuid(),
|
id: apoc.create.uuid(),
|
||||||
indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END,
|
indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END,
|
||||||
content: LEFT($content,2000),
|
content: LEFT($content,2000),
|
||||||
saved: true,
|
saved: true,
|
||||||
distributed: false,
|
distributed: false,
|
||||||
seen: false
|
seen: false
|
||||||
})-[:INSIDE]->(room)
|
})-[:INSIDE]->(room)
|
||||||
SET room.lastMessageAt = toString(datetime())
|
SET room.lastMessageAt = toString(datetime())
|
||||||
RETURN message {
|
RETURN message {
|
||||||
.*,
|
.*,
|
||||||
indexId: toString(message.indexId),
|
indexId: toString(message.indexId),
|
||||||
recipientId: recipientUser.id,
|
recipientId: recipientUser.id,
|
||||||
senderId: currentUser.id,
|
senderId: currentUser.id,
|
||||||
username: currentUser.name,
|
username: currentUser.name,
|
||||||
avatar: image.url,
|
avatar: image.url,
|
||||||
date: message.createdAt
|
date: message.createdAt
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const createMessageTxResponse = await transaction.run(createMessageCypher, {
|
const createMessageTxResponse = await transaction.run(createMessageCypher, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
roomId,
|
roomId,
|
||||||
content,
|
content,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [message] = await createMessageTxResponse.records.map((record) =>
|
const [message] = createMessageTxResponse.records.map((record) => record.get('message'))
|
||||||
record.get('message'),
|
|
||||||
)
|
|
||||||
|
|
||||||
// this is the case if the room doesn't exist - requires refactoring for implicit rooms
|
// this is the case if the room doesn't exist - requires refactoring for implicit rooms
|
||||||
if (!message) {
|
if (!message) {
|
||||||
@ -142,38 +140,32 @@ export default {
|
|||||||
|
|
||||||
return { ...message, files: atns }
|
return { ...message, files: atns }
|
||||||
})
|
})
|
||||||
|
|
||||||
return await writeTxResultPromise
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MarkMessagesAsSeen: async (_parent, params, context, _resolveInfo) => {
|
MarkMessagesAsSeen: async (_parent, params, context, _resolveInfo) => {
|
||||||
const { messageIds } = params
|
const { messageIds } = params
|
||||||
const currentUserId = context.user.id
|
const currentUserId = context.user.id
|
||||||
const session = context.driver.session()
|
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 {
|
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
|
// send subscription to author to updated the messages
|
||||||
return true
|
return true
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export default {
|
|||||||
const [reviewed] = await reviewWriteTxResultPromise
|
const [reviewed] = await reviewWriteTxResultPromise
|
||||||
return reviewed || null
|
return reviewed || null
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export default {
|
|||||||
const notifications = await readTxResultPromise
|
const notifications = await readTxResultPromise
|
||||||
return notifications
|
return notifications
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -111,7 +111,7 @@ export default {
|
|||||||
const [notifications] = await writeTxResultPromise
|
const [notifications] = await writeTxResultPromise
|
||||||
return notifications
|
return notifications
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
markAllAsRead: async (parent, args, context, _resolveInfo) => {
|
markAllAsRead: async (parent, args, context, _resolveInfo) => {
|
||||||
@ -140,7 +140,7 @@ export default {
|
|||||||
const notifications = await writeTxResultPromise
|
const notifications = await writeTxResultPromise
|
||||||
return notifications
|
return notifications
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export default {
|
|||||||
const [reset] = await passwordResetTxPromise
|
const [reset] = await passwordResetTxPromise
|
||||||
return !!reset?.properties.usedAt
|
return !!reset?.properties.usedAt
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export default {
|
|||||||
const [emotionsCount] = await readTxResultPromise
|
const [emotionsCount] = await readTxResultPromise
|
||||||
return emotionsCount
|
return emotionsCount
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PostsEmotionsByCurrentUser: async (_object, params, context: Context, _resolveInfo) => {
|
PostsEmotionsByCurrentUser: async (_object, params, context: Context, _resolveInfo) => {
|
||||||
@ -364,7 +364,7 @@ export default {
|
|||||||
const [emoted] = await writeTxResultPromise
|
const [emoted] = await writeTxResultPromise
|
||||||
return emoted
|
return emoted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pinPost: async (_parent, params, context: Context, _resolveInfo) => {
|
pinPost: async (_parent, params, context: Context, _resolveInfo) => {
|
||||||
@ -464,7 +464,7 @@ export default {
|
|||||||
try {
|
try {
|
||||||
;[unpinnedPost] = await writeTxResultPromise
|
;[unpinnedPost] = await writeTxResultPromise
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
return unpinnedPost
|
return unpinnedPost
|
||||||
},
|
},
|
||||||
@ -552,7 +552,7 @@ export default {
|
|||||||
post.viewedTeaserCount = post.viewedTeaserCount.low
|
post.viewedTeaserCount = post.viewedTeaserCount.low
|
||||||
return post
|
return post
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleObservePost: async (_parent, params, context, _resolveInfo) => {
|
toggleObservePost: async (_parent, params, context, _resolveInfo) => {
|
||||||
@ -581,7 +581,7 @@ export default {
|
|||||||
post.viewedTeaserCount = post.viewedTeaserCount.low
|
post.viewedTeaserCount = post.viewedTeaserCount.low
|
||||||
return post
|
return post
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pushPost: async (_parent, params, context: Context, _resolveInfo) => {
|
pushPost: async (_parent, params, context: Context, _resolveInfo) => {
|
||||||
@ -705,7 +705,7 @@ export default {
|
|||||||
const relatedContributions = await writeTxResultPromise
|
const relatedContributions = await writeTxResultPromise
|
||||||
return relatedContributions
|
return relatedContributions
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export default {
|
|||||||
const [filedReport] = await fileReportWriteTxResultPromise
|
const [filedReport] = await fileReportWriteTxResultPromise
|
||||||
return filedReport || null
|
return filedReport || null
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -108,7 +108,7 @@ export default {
|
|||||||
const reports = await reportsReadTxPromise
|
const reports = await reportsReadTxPromise
|
||||||
return reports || []
|
return reports || []
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -143,7 +143,7 @@ export default {
|
|||||||
return relationshipWithNestedAttributes
|
return relationshipWithNestedAttributes
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
return filed
|
return filed
|
||||||
}, */
|
}, */
|
||||||
@ -177,7 +177,7 @@ export default {
|
|||||||
return relationshipWithNestedAttributes
|
return relationshipWithNestedAttributes
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
return reviewed
|
return reviewed
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
||||||
/* eslint-disable @typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/require-await */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
@ -53,7 +52,7 @@ export default {
|
|||||||
const count = await getUnreadRoomsCount(currentUserId, session)
|
const count = await getUnreadRoomsCount(currentUserId, session)
|
||||||
return count
|
return count
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -67,43 +66,40 @@ export default {
|
|||||||
throw new Error('Cannot create a room with self')
|
throw new Error('Cannot create a room with self')
|
||||||
}
|
}
|
||||||
const session = context.driver.session()
|
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 {
|
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) {
|
if (room) {
|
||||||
room.roomId = room.id
|
room.roomId = room.id
|
||||||
}
|
}
|
||||||
return room
|
return room
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -134,7 +134,7 @@ const getSearchResults = async (context, setup, params, resultCallback = searchR
|
|||||||
const results = await searchResultPromise(session, setup, params)
|
const results = await searchResultPromise(session, setup, params)
|
||||||
return resultCallback(results)
|
return resultCallback(results)
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default {
|
|||||||
const [isShouted] = await shoutWriteTxResultPromise
|
const [isShouted] = await shoutWriteTxResultPromise
|
||||||
return isShouted
|
return isShouted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ export default {
|
|||||||
const [isShouted] = await unshoutWriteTxResultPromise
|
const [isShouted] = await unshoutWriteTxResultPromise
|
||||||
return isShouted
|
return isShouted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -39,21 +38,16 @@ export default {
|
|||||||
})
|
})
|
||||||
} AS result`
|
} AS result`
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const resultPromise = session.readTransaction(async (transaction) => {
|
|
||||||
const transactionResponse = transaction.run(cypher, {
|
|
||||||
id,
|
|
||||||
})
|
|
||||||
return transactionResponse
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
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')
|
const userData = result.records[0].get('result')
|
||||||
userData.posts.sort(byCreationDate)
|
userData.posts.sort(byCreationDate)
|
||||||
userData.posts.forEach((post) => post.comments.sort(byCreationDate))
|
userData.posts.forEach((post) => post.comments.sort(byCreationDate))
|
||||||
return userData
|
return userData
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1066,7 +1066,7 @@ describe('setTrophyBadgeSelected', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
expect.objectContaining({
|
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({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
message: 'Error: You cannot set badges not rewarded to you.',
|
message: 'You cannot set badges not rewarded to you.',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -49,21 +49,19 @@ export default {
|
|||||||
User: async (object, args, context, resolveInfo) => {
|
User: async (object, args, context, resolveInfo) => {
|
||||||
if (args.email) {
|
if (args.email) {
|
||||||
args.email = normalizeEmail(args.email)
|
args.email = normalizeEmail(args.email)
|
||||||
let session
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
session = context.driver.session()
|
|
||||||
const readTxResult = await session.readTransaction((txc) => {
|
const readTxResult = await session.readTransaction((txc) => {
|
||||||
const result = txc.run(
|
return txc.run(
|
||||||
`
|
`
|
||||||
MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $args.email})
|
MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $args.email})
|
||||||
RETURN user {.*, email: e.email}`,
|
RETURN user {.*, email: e.email}`,
|
||||||
{ args },
|
{ args },
|
||||||
)
|
)
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
return readTxResult.records.map((r) => r.get('user'))
|
return readTxResult.records.map((r) => r.get('user'))
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return neo4jgraphql(object, args, context, resolveInfo)
|
return neo4jgraphql(object, args, context, resolveInfo)
|
||||||
@ -105,28 +103,27 @@ export default {
|
|||||||
if (currentUser.id === args.id) return null
|
if (currentUser.id === args.id) return null
|
||||||
|
|
||||||
const session = context.driver.session()
|
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 {
|
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) {
|
if (!blockedUser) {
|
||||||
throw new UserInputError('Could not find User')
|
throw new UserInputError('Could not find User')
|
||||||
}
|
}
|
||||||
return blockedUser
|
return blockedUser
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unblockUser: async (_object, args, context, _resolveInfo) => {
|
unblockUser: async (_object, args, context, _resolveInfo) => {
|
||||||
@ -134,25 +131,22 @@ export default {
|
|||||||
if (currentUser.id === args.id) return null
|
if (currentUser.id === args.id) return null
|
||||||
|
|
||||||
const session = context.driver.session()
|
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 {
|
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) {
|
if (!unblockedUser) {
|
||||||
throw new Error('Could not find blocked User')
|
throw new UserInputError('Could not find blocked User')
|
||||||
}
|
}
|
||||||
return unblockedUser
|
return unblockedUser
|
||||||
} catch {
|
|
||||||
throw new UserInputError('Could not find blocked User')
|
|
||||||
} finally {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -183,103 +177,100 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const session = context.driver.session()
|
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 {
|
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"
|
// TODO: put in a middleware, see "CreateGroup", "UpdateGroup"
|
||||||
await createOrUpdateLocations('User', params.id, params.locationName, session, context)
|
await createOrUpdateLocations('User', params.id, params.locationName, session, context)
|
||||||
return user
|
return user
|
||||||
} catch (error) {
|
|
||||||
throw new UserInputError(error.message)
|
|
||||||
} finally {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DeleteUser: async (_object, params, context: Context, _resolveInfo) => {
|
DeleteUser: async (_object, params, context: Context, _resolveInfo) => {
|
||||||
const { resource, id: userId } = params
|
const { resource, id: userId } = params
|
||||||
|
const allowedLabels = ['Post', 'Comment']
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
const user = await deleteUserTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
return user
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -289,24 +280,26 @@ export default {
|
|||||||
|
|
||||||
if (context.user.id === id) throw new Error('you-cannot-change-your-own-role')
|
if (context.user.id === id) throw new Error('you-cannot-change-your-own-role')
|
||||||
const session = context.driver.session()
|
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 {
|
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
|
return user
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveCategorySettings: async (_object, args, context, _resolveInfo) => {
|
saveCategorySettings: async (_object, args, context, _resolveInfo) => {
|
||||||
@ -316,40 +309,30 @@ export default {
|
|||||||
} = context
|
} = context
|
||||||
|
|
||||||
const session = context.driver.session()
|
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 {
|
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
|
return true
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateOnlineStatus: async (_object, args, context: Context, _resolveInfo) => {
|
updateOnlineStatus: async (_object, args, context: Context, _resolveInfo) => {
|
||||||
@ -391,41 +374,37 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const session = context.driver.session()
|
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 {
|
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) {
|
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
|
return user
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetTrophyBadgesSelected: async (_object, _args, context, _resolveInfo) => {
|
resetTrophyBadgesSelected: async (_object, _args, context, _resolveInfo) => {
|
||||||
@ -434,25 +413,21 @@ export default {
|
|||||||
} = context
|
} = context
|
||||||
|
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await query
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const result = await transaction.run(
|
||||||
throw new Error(error)
|
`
|
||||||
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -539,97 +514,80 @@ export default {
|
|||||||
},
|
},
|
||||||
badgeTrophiesSelected: async (parent, _params, context, _resolveInfo) => {
|
badgeTrophiesSelected: async (parent, _params, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
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 {
|
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)
|
const result = Array(TROPHY_BADGES_SELECTED_MAX).fill(defaultTrophyBadge)
|
||||||
badgesSelected.map((record) => {
|
badgesSelected.forEach((record) => {
|
||||||
result[record.get('slot')] = record.get('badge')
|
result[record.get('slot')] = record.get('badge')
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
badgeTrophiesUnused: async (parent, _params, context, _resolveInfo) => {
|
badgeTrophiesUnused: async (parent, _params, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await query
|
return await session.readTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const result = await transaction.run(
|
||||||
throw new Error(error)
|
`
|
||||||
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
badgeTrophiesUnusedCount: async (parent, _params, context, _resolveInfo) => {
|
badgeTrophiesUnusedCount: async (parent, _params, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await query
|
return await session.readTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const result = await transaction.run(
|
||||||
throw new Error(error)
|
`
|
||||||
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
badgeVerification: async (parent, _params, context, _resolveInfo) => {
|
badgeVerification: async (parent, _params, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
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 {
|
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
|
return result ?? defaultVerificationBadge
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...Resolver('User', {
|
...Resolver('User', {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -159,7 +159,7 @@ const postAuthorOfComment = async (commentId, { context }) => {
|
|||||||
})
|
})
|
||||||
return postAuthorId.records.map((record) => record.get('authorId'))
|
return postAuthorId.records.map((record) => record.get('authorId'))
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,21 +186,18 @@ const notifyFollowingUsers = async (postId, groupId, context) => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||||
throw new Error(error)
|
postId,
|
||||||
|
reason,
|
||||||
|
groupId: groupId || null,
|
||||||
|
userId: context.user.id,
|
||||||
|
})
|
||||||
|
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,21 +229,18 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||||
throw new Error(error)
|
postId,
|
||||||
|
reason,
|
||||||
|
groupId,
|
||||||
|
userId: context.user.id,
|
||||||
|
})
|
||||||
|
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||||
|
})
|
||||||
} finally {
|
} 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) }
|
RETURN notification {.*, from: finalGroup, to: properties(owner), email: email, relatedUser: properties(user) }
|
||||||
`
|
`
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||||
throw new Error(error)
|
groupId,
|
||||||
|
reason,
|
||||||
|
userId,
|
||||||
|
})
|
||||||
|
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||||
|
})
|
||||||
} finally {
|
} 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) }
|
RETURN notification {.*, from: finalGroup, to: properties(user), email: email, relatedUser: properties(owner) }
|
||||||
`
|
`
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||||
throw new Error(error)
|
groupId,
|
||||||
|
reason,
|
||||||
|
userId,
|
||||||
|
ownerId: owner.id,
|
||||||
|
})
|
||||||
|
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||||
|
})
|
||||||
} finally {
|
} 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) }
|
RETURN notification {.*, from: finalResource, to: properties(user), email: email, relatedUser: properties(user) }
|
||||||
`
|
`
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
return await writeTxResultPromise
|
return await session.writeTransaction(async (transaction) => {
|
||||||
} catch (error) {
|
const notificationTransactionResponse = await transaction.run(mentionedCypher, {
|
||||||
throw new Error(error)
|
id,
|
||||||
|
idsOfUsers,
|
||||||
|
reason,
|
||||||
|
})
|
||||||
|
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
||||||
await validateNotifyUsers(label, reason)
|
await validateNotifyUsers(label, reason)
|
||||||
const session = context.driver.session()
|
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 {
|
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 {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,30 +431,29 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
|
|||||||
|
|
||||||
// Find Recipient
|
// Find Recipient
|
||||||
const session = context.driver.session()
|
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 {
|
try {
|
||||||
// Execute Query
|
const { senderUser, recipientUser, email } = await session.readTransaction(
|
||||||
const { senderUser, recipientUser, email } = await messageRecipient
|
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) {
|
if (recipientUser) {
|
||||||
// send subscriptions
|
// send subscriptions
|
||||||
@ -493,10 +476,8 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
|
|||||||
|
|
||||||
// Return resolver result to client
|
// Return resolver result to client
|
||||||
return message
|
return message
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error)
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ const setPostCounter = async (postId, relation, context) => {
|
|||||||
return txc.run(createRelatedCypher(relation), { currentUser, postId })
|
return txc.run(createRelatedCypher(relation), { currentUser, postId })
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const validateCreateComment = async (resolve, root, args, context, info) => {
|
|||||||
return resolve(root, args, context, info)
|
return resolve(root, args, context, info)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,28 +64,27 @@ const validateReview = async (resolve, root, args, context, info) => {
|
|||||||
const { user, driver } = context
|
const { user, driver } = context
|
||||||
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
||||||
const session = driver.session()
|
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 {
|
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
|
existingReportedResource = txResult
|
||||||
if (!existingReportedResource?.length)
|
if (!existingReportedResource?.length)
|
||||||
throw new Error(`Resource not found or is not a Post|Comment|User!`)
|
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)
|
if (authorId && authorId === user.id)
|
||||||
throw new Error(`You cannot review your own ${existingReportedResource.label}!`)
|
throw new Error(`You cannot review your own ${existingReportedResource.label}!`)
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(root, args, context, info)
|
return resolve(root, args, context, info)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import helmet from 'helmet'
|
|||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
import { context, getContext } from './context'
|
import { context, getContext } from './context'
|
||||||
import schema from './graphql/schema'
|
import schema from './graphql/schema'
|
||||||
|
import logger from './logger'
|
||||||
import middleware from './middleware'
|
import middleware from './middleware'
|
||||||
|
|
||||||
import type { ApolloServerExpressConfig } from 'apollo-server-express'
|
import type { ApolloServerExpressConfig } from 'apollo-server-express'
|
||||||
@ -24,8 +25,12 @@ const createServer = (options?: ApolloServerExpressConfig) => {
|
|||||||
context,
|
context,
|
||||||
schema: middleware(schema),
|
schema: middleware(schema),
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
|
keepAlive: 10000,
|
||||||
onConnect: (connectionParams) =>
|
onConnect: (connectionParams) =>
|
||||||
getContext()(connectionParams as { headers: { authorization?: string } }),
|
getContext()(connectionParams as { headers: { authorization?: string } }),
|
||||||
|
onDisconnect: () => {
|
||||||
|
logger.debug('WebSocket client disconnected')
|
||||||
|
},
|
||||||
},
|
},
|
||||||
debug: !!CONFIG.DEBUG,
|
debug: !!CONFIG.DEBUG,
|
||||||
uploads: false,
|
uploads: false,
|
||||||
|
|||||||
@ -5,11 +5,22 @@ import type { S3Config } from '@config/index'
|
|||||||
|
|
||||||
import { FileUploadCallback, FileDeleteCallback } from './types'
|
import { FileUploadCallback, FileDeleteCallback } from './types'
|
||||||
|
|
||||||
export const s3Service = (config: S3Config, prefix: string) => {
|
let cachedClient: S3Client | null = null
|
||||||
const { AWS_BUCKET: Bucket } = config
|
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 { AWS_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = config
|
||||||
const s3 = new S3Client({
|
cachedClient = new S3Client({
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: AWS_ACCESS_KEY_ID,
|
accessKeyId: AWS_ACCESS_KEY_ID,
|
||||||
secretAccessKey: AWS_SECRET_ACCESS_KEY,
|
secretAccessKey: AWS_SECRET_ACCESS_KEY,
|
||||||
@ -17,6 +28,13 @@ export const s3Service = (config: S3Config, prefix: string) => {
|
|||||||
endpoint: AWS_ENDPOINT,
|
endpoint: AWS_ENDPOINT,
|
||||||
forcePathStyle: true,
|
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 uploadFile: FileUploadCallback = async ({ createReadStream, uniqueFilename, mimetype }) => {
|
||||||
const s3Location = prefix.length > 0 ? `${prefix}/${uniqueFilename}` : uniqueFilename
|
const s3Location = prefix.length > 0 ? `${prefix}/${uniqueFilename}` : uniqueFilename
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user