mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-03-01 12:44:37 +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) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -48,7 +48,7 @@ export default {
|
||||
const [comment] = await writeTxResultPromise
|
||||
return comment
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
UpdateComment: async (_parent, params, context, _resolveInfo) => {
|
||||
@ -71,7 +71,7 @@ export default {
|
||||
const [comment] = await writeTxResultPromise
|
||||
return comment
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
DeleteComment: async (_parent, args, context, _resolveInfo) => {
|
||||
@ -95,7 +95,7 @@ export default {
|
||||
const [comment] = await writeTxResultPromise
|
||||
return comment
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,59 +6,51 @@ export default {
|
||||
Query: {
|
||||
Donations: async (_parent, _params, context, _resolveInfo) => {
|
||||
const { driver } = context
|
||||
let donations
|
||||
const session = driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
const donationsTransactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (donations:Donations)
|
||||
WITH donations LIMIT 1
|
||||
RETURN donations
|
||||
`,
|
||||
{},
|
||||
)
|
||||
return donationsTransactionResponse.records.map(
|
||||
(record) => record.get('donations').properties,
|
||||
)
|
||||
})
|
||||
try {
|
||||
const txResult = await writeTxResultPromise
|
||||
if (!txResult[0]) return null
|
||||
donations = txResult[0]
|
||||
const txResult = await session.readTransaction(async (txc) => {
|
||||
const donationsTransactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (donations:Donations)
|
||||
WITH donations LIMIT 1
|
||||
RETURN donations
|
||||
`,
|
||||
{},
|
||||
)
|
||||
return donationsTransactionResponse.records.map(
|
||||
(record) => record.get('donations').properties,
|
||||
)
|
||||
})
|
||||
return txResult[0] || null
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
return donations
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
UpdateDonations: async (_parent, params, context, _resolveInfo) => {
|
||||
const { driver } = context
|
||||
let donations
|
||||
const session = driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
const updateDonationsTransactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (donations:Donations)
|
||||
WITH donations LIMIT 1
|
||||
SET donations += $params
|
||||
SET donations.updatedAt = toString(datetime())
|
||||
RETURN donations
|
||||
`,
|
||||
{ params },
|
||||
)
|
||||
return updateDonationsTransactionResponse.records.map(
|
||||
(record) => record.get('donations').properties,
|
||||
)
|
||||
})
|
||||
try {
|
||||
const txResult = await writeTxResultPromise
|
||||
if (!txResult[0]) return null
|
||||
donations = txResult[0]
|
||||
const txResult = await session.writeTransaction(async (txc) => {
|
||||
const updateDonationsTransactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (donations:Donations)
|
||||
WITH donations LIMIT 1
|
||||
SET donations += $params
|
||||
SET donations.updatedAt = toString(datetime())
|
||||
RETURN donations
|
||||
`,
|
||||
{ params },
|
||||
)
|
||||
return updateDonationsTransactionResponse.records.map(
|
||||
(record) => record.get('donations').properties,
|
||||
)
|
||||
})
|
||||
return txResult[0] || null
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
return donations
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
@ -17,27 +16,24 @@ export default {
|
||||
VerifyNonce: async (_parent, args, context, _resolveInfo) => {
|
||||
args.email = normalizeEmail(args.email)
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`
|
||||
MATCH (email:EmailAddress {email: $email, nonce: $nonce})
|
||||
RETURN count(email) > 0 AS result
|
||||
`,
|
||||
{ email: args.email, nonce: args.nonce },
|
||||
)
|
||||
return result
|
||||
})
|
||||
try {
|
||||
const txResult = await readTxResultPromise
|
||||
const txResult = await session.readTransaction(async (txc) => {
|
||||
return await txc.run(
|
||||
`
|
||||
MATCH (email:EmailAddress {email: $email, nonce: $nonce})
|
||||
RETURN count(email) > 0 AS result
|
||||
`,
|
||||
{ email: args.email, nonce: args.nonce },
|
||||
)
|
||||
})
|
||||
return txResult.records[0].get('result')
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
AddEmailAddress: async (_parent, args, context, _resolveInfo) => {
|
||||
let response
|
||||
args.email = normalizeEmail(args.email)
|
||||
try {
|
||||
const { neode } = context
|
||||
@ -57,65 +53,64 @@ export default {
|
||||
} = context
|
||||
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})
|
||||
MERGE (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
|
||||
SET email.createdAt = toString(datetime())
|
||||
RETURN email, user
|
||||
`,
|
||||
{ userId, email: args.email, nonce },
|
||||
)
|
||||
return result.records.map((record) => ({
|
||||
name: record.get('user').properties.name,
|
||||
locale: record.get('user').properties.locale,
|
||||
...record.get('email').properties,
|
||||
}))
|
||||
})
|
||||
try {
|
||||
const txResult = await writeTxResultPromise
|
||||
response = txResult[0]
|
||||
const txResult = await session.writeTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})
|
||||
MERGE (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
|
||||
SET email.createdAt = toString(datetime())
|
||||
RETURN email, user
|
||||
`,
|
||||
{ userId, email: args.email, nonce },
|
||||
)
|
||||
return result.records.map((record) => ({
|
||||
name: record.get('user').properties.name,
|
||||
locale: record.get('user').properties.locale,
|
||||
...record.get('email').properties,
|
||||
}))
|
||||
})
|
||||
const response = txResult[0]
|
||||
if (!response) throw new UserInputError('User not found.')
|
||||
return response
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
return response
|
||||
},
|
||||
VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => {
|
||||
let response
|
||||
const {
|
||||
user: { id: userId },
|
||||
} = context
|
||||
args.email = normalizeEmail(args.email)
|
||||
const { nonce, email } = args
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})-[:PRIMARY_EMAIL]->(previous:EmailAddress)
|
||||
MATCH (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
|
||||
OPTIONAL MATCH (abandonedEmail:EmailAddress{email: $email}) WHERE NOT EXISTS ((abandonedEmail)<-[]-())
|
||||
DELETE abandonedEmail
|
||||
MERGE (user)-[:PRIMARY_EMAIL]->(email)
|
||||
SET email:EmailAddress
|
||||
SET email.verifiedAt = toString(datetime())
|
||||
REMOVE email:UnverifiedEmailAddress
|
||||
DETACH DELETE previous
|
||||
RETURN email
|
||||
`,
|
||||
{ userId, email, nonce },
|
||||
)
|
||||
return result.records.map((record) => record.get('email').properties)
|
||||
})
|
||||
let response
|
||||
try {
|
||||
const txResult = await writeTxResultPromise
|
||||
const txResult = await session.writeTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})-[:PRIMARY_EMAIL]->(previous:EmailAddress)
|
||||
MATCH (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
|
||||
OPTIONAL MATCH (abandonedEmail:EmailAddress{email: $email}) WHERE NOT EXISTS ((abandonedEmail)<-[]-())
|
||||
DELETE abandonedEmail
|
||||
MERGE (user)-[:PRIMARY_EMAIL]->(email)
|
||||
SET email:EmailAddress
|
||||
SET email.verifiedAt = toString(datetime())
|
||||
REMOVE email:UnverifiedEmailAddress
|
||||
DETACH DELETE previous
|
||||
RETURN email
|
||||
`,
|
||||
{ userId, email, nonce },
|
||||
)
|
||||
return result.records.map((record) => record.get('email').properties)
|
||||
})
|
||||
response = txResult[0]
|
||||
} catch (e) {
|
||||
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
throw new UserInputError('A user account with this email already exists.')
|
||||
throw new Error(e)
|
||||
throw e
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
if (!response) throw new UserInputError('Invalid nonce or no email address found.')
|
||||
return response
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
@ -13,10 +12,7 @@ import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '@constants/groups'
|
||||
import { removeHtmlTags } from '@middleware/helpers/cleanHtml'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
import Resolver, {
|
||||
removeUndefinedNullValuesFromObject,
|
||||
convertObjectToCypherMapLiteral,
|
||||
} from './helpers/Resolver'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import { images } from './images/images'
|
||||
import { createOrUpdateLocations } from './users/location'
|
||||
|
||||
@ -24,35 +20,40 @@ export default {
|
||||
Query: {
|
||||
Group: async (_object, params, context: Context, _resolveInfo) => {
|
||||
const { isMember, id, slug, first, offset } = params
|
||||
const matchParams = { id, slug }
|
||||
removeUndefinedNullValuesFromObject(matchParams)
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const transactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (group:Group${convertObjectToCypherMapLiteral(matchParams, true)})
|
||||
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
||||
WITH group, membership
|
||||
${(isMember === true && "WHERE membership IS NOT NULL AND (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
|
||||
${(isMember === false && "WHERE membership IS NULL AND (group.groupType IN ['public', 'closed'])") || ''}
|
||||
${(isMember === undefined && "WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
ORDER BY group.createdAt DESC
|
||||
${first !== undefined && offset !== undefined ? `SKIP ${offset} LIMIT ${first}` : ''}
|
||||
`,
|
||||
{
|
||||
userId: context.user.id,
|
||||
},
|
||||
)
|
||||
return transactionResponse.records.map((record) => record.get('group'))
|
||||
})
|
||||
try {
|
||||
return await readTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.readTransaction(async (txc) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const matchFilters: string[] = []
|
||||
if (id !== undefined) matchFilters.push('group.id = $id')
|
||||
if (slug !== undefined) matchFilters.push('group.slug = $slug')
|
||||
const matchWhere = matchFilters.length ? `WHERE ${matchFilters.join(' AND ')}` : ''
|
||||
|
||||
const transactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (group:Group)
|
||||
${matchWhere}
|
||||
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
||||
WITH group, membership
|
||||
${(isMember === true && "WHERE membership IS NOT NULL AND (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
|
||||
${(isMember === false && "WHERE membership IS NULL AND (group.groupType IN ['public', 'closed'])") || ''}
|
||||
${(isMember === undefined && "WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
ORDER BY group.createdAt DESC
|
||||
${first !== undefined && offset !== undefined ? 'SKIP toInteger($offset) LIMIT toInteger($first)' : ''}
|
||||
`,
|
||||
{
|
||||
userId: context.user.id,
|
||||
id,
|
||||
slug,
|
||||
first,
|
||||
offset,
|
||||
},
|
||||
)
|
||||
return transactionResponse.records.map((record) => record.get('group'))
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -60,25 +61,22 @@ export default {
|
||||
GroupMembers: async (_object, params, context: Context, _resolveInfo) => {
|
||||
const { id: groupId, first = 25, offset = 0 } = params
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const groupMemberCypher = `
|
||||
MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
|
||||
RETURN user {.*}, membership {.*}
|
||||
SKIP toInteger($offset) LIMIT toInteger($first)
|
||||
`
|
||||
const transactionResponse = await txc.run(groupMemberCypher, {
|
||||
groupId,
|
||||
first,
|
||||
offset,
|
||||
})
|
||||
return transactionResponse.records.map((record) => {
|
||||
return { user: record.get('user'), membership: record.get('membership') }
|
||||
})
|
||||
})
|
||||
try {
|
||||
return await readTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.readTransaction(async (txc) => {
|
||||
const groupMemberCypher = `
|
||||
MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
|
||||
RETURN user {.*}, membership {.*}
|
||||
SKIP toInteger($offset) LIMIT toInteger($first)
|
||||
`
|
||||
const transactionResponse = await txc.run(groupMemberCypher, {
|
||||
groupId,
|
||||
first,
|
||||
offset,
|
||||
})
|
||||
return transactionResponse.records.map((record) => {
|
||||
return { user: record.get('user'), membership: record.get('membership') }
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -89,31 +87,29 @@ export default {
|
||||
user: { id: userId },
|
||||
} = context
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
let cypher
|
||||
if (isMember) {
|
||||
cypher = `MATCH (user:User)-[membership:MEMBER_OF]->(group:Group)
|
||||
WHERE user.id = $userId
|
||||
AND membership.role IN ['usual', 'admin', 'owner', 'pending']
|
||||
RETURN toString(count(group)) AS count`
|
||||
} else {
|
||||
cypher = `MATCH (group:Group)
|
||||
OPTIONAL MATCH (user:User)-[membership:MEMBER_OF]->(group)
|
||||
WHERE user.id = $userId
|
||||
WITH group, membership
|
||||
WHERE group.groupType IN ['public', 'closed']
|
||||
OR membership.role IN ['usual', 'admin', 'owner']
|
||||
RETURN toString(count(group)) AS count`
|
||||
}
|
||||
const transactionResponse = await txc.run(cypher, { userId })
|
||||
return transactionResponse.records.map((record) => record.get('count'))
|
||||
})
|
||||
try {
|
||||
return parseInt(await readTxResultPromise)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
const result = await session.readTransaction(async (txc) => {
|
||||
let cypher
|
||||
if (isMember) {
|
||||
cypher = `MATCH (user:User)-[membership:MEMBER_OF]->(group:Group)
|
||||
WHERE user.id = $userId
|
||||
AND membership.role IN ['usual', 'admin', 'owner', 'pending']
|
||||
RETURN toString(count(group)) AS count`
|
||||
} else {
|
||||
cypher = `MATCH (group:Group)
|
||||
OPTIONAL MATCH (user:User)-[membership:MEMBER_OF]->(group)
|
||||
WHERE user.id = $userId
|
||||
WITH group, membership
|
||||
WHERE group.groupType IN ['public', 'closed']
|
||||
OR membership.role IN ['usual', 'admin', 'owner']
|
||||
RETURN toString(count(group)) AS count`
|
||||
}
|
||||
const transactionResponse = await txc.run(cypher, { userId })
|
||||
return transactionResponse.records.map((record) => record.get('count'))[0]
|
||||
})
|
||||
return parseInt(result, 10) || 0
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -138,52 +134,51 @@ export default {
|
||||
}
|
||||
params.id = params.id || uuid()
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const categoriesCypher =
|
||||
config.CATEGORIES_ACTIVE && categoryIds
|
||||
? `
|
||||
WITH group, membership
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (group)-[:CATEGORIZED]->(category)
|
||||
`
|
||||
: ''
|
||||
const ownerCreateGroupTransactionResponse = await transaction.run(
|
||||
`
|
||||
CREATE (group:Group)
|
||||
SET group += $params
|
||||
SET group.createdAt = toString(datetime())
|
||||
SET group.updatedAt = toString(datetime())
|
||||
WITH group
|
||||
MATCH (owner:User {id: $userId})
|
||||
MERGE (owner)-[:CREATED]->(group)
|
||||
MERGE (owner)-[membership:MEMBER_OF]->(group)
|
||||
SET
|
||||
membership.createdAt = toString(datetime()),
|
||||
membership.updatedAt = null,
|
||||
membership.role = 'owner'
|
||||
${categoriesCypher}
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
`,
|
||||
{ userId: context.user.id, categoryIds, params },
|
||||
)
|
||||
const [group] = ownerCreateGroupTransactionResponse.records.map((record) =>
|
||||
record.get('group'),
|
||||
)
|
||||
return group
|
||||
})
|
||||
try {
|
||||
const group = await writeTxResultPromise
|
||||
const group = await session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const categoriesCypher =
|
||||
config.CATEGORIES_ACTIVE && categoryIds
|
||||
? `
|
||||
WITH group, membership
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (group)-[:CATEGORIZED]->(category)
|
||||
`
|
||||
: ''
|
||||
const ownerCreateGroupTransactionResponse = await transaction.run(
|
||||
`
|
||||
CREATE (group:Group)
|
||||
SET group += $params
|
||||
SET group.createdAt = toString(datetime())
|
||||
SET group.updatedAt = toString(datetime())
|
||||
WITH group
|
||||
MATCH (owner:User {id: $userId})
|
||||
MERGE (owner)-[:CREATED]->(group)
|
||||
MERGE (owner)-[membership:MEMBER_OF]->(group)
|
||||
SET
|
||||
membership.createdAt = toString(datetime()),
|
||||
membership.updatedAt = null,
|
||||
membership.role = 'owner'
|
||||
${categoriesCypher}
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
`,
|
||||
{ userId: context.user.id, categoryIds, params },
|
||||
)
|
||||
const [group] = ownerCreateGroupTransactionResponse.records.map((record) =>
|
||||
record.get('group'),
|
||||
)
|
||||
return group
|
||||
})
|
||||
// TODO: put in a middleware, see "UpdateGroup", "UpdateUser"
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
||||
return group
|
||||
} catch (error) {
|
||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
throw new UserInputError('Group with this slug already exists!')
|
||||
throw new Error(error)
|
||||
throw error
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -211,61 +206,59 @@ export default {
|
||||
throw new UserInputError('Description too short!')
|
||||
}
|
||||
const session = context.driver.session()
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category)
|
||||
DELETE previousRelations
|
||||
RETURN group, category
|
||||
`
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(cypherDeletePreviousRelations, { groupId })
|
||||
})
|
||||
}
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
let updateGroupCypher = `
|
||||
MATCH (group:Group {id: $groupId})
|
||||
SET group += $params
|
||||
SET group.updatedAt = toString(datetime())
|
||||
WITH group
|
||||
`
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
updateGroupCypher += `
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (group)-[:CATEGORIZED]->(category)
|
||||
try {
|
||||
const group = await session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
await transaction.run(
|
||||
`
|
||||
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(:Category)
|
||||
DELETE previousRelations
|
||||
`,
|
||||
{ groupId },
|
||||
)
|
||||
}
|
||||
let updateGroupCypher = `
|
||||
MATCH (group:Group {id: $groupId})
|
||||
SET group += $params
|
||||
SET group.updatedAt = toString(datetime())
|
||||
WITH group
|
||||
`
|
||||
}
|
||||
updateGroupCypher += `
|
||||
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
`
|
||||
const transactionResponse = await transaction.run(updateGroupCypher, {
|
||||
groupId,
|
||||
userId: context.user.id,
|
||||
categoryIds,
|
||||
params,
|
||||
})
|
||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||
if (avatarInput) {
|
||||
await images(context.config).mergeImage(group, 'AVATAR_IMAGE', avatarInput, {
|
||||
transaction,
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
updateGroupCypher += `
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (group)-[:CATEGORIZED]->(category)
|
||||
WITH group
|
||||
`
|
||||
}
|
||||
updateGroupCypher += `
|
||||
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
`
|
||||
const transactionResponse = await transaction.run(updateGroupCypher, {
|
||||
groupId,
|
||||
userId: context.user.id,
|
||||
categoryIds,
|
||||
params,
|
||||
})
|
||||
}
|
||||
return group
|
||||
})
|
||||
try {
|
||||
const group = await writeTxResultPromise
|
||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||
if (avatarInput) {
|
||||
await images(context.config).mergeImage(group, 'AVATAR_IMAGE', avatarInput, {
|
||||
transaction,
|
||||
})
|
||||
}
|
||||
return group
|
||||
})
|
||||
// TODO: put in a middleware, see "CreateGroup", "UpdateUser"
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
||||
return group
|
||||
} catch (error) {
|
||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
throw new UserInputError('Group with this slug already exists!')
|
||||
throw new Error(error)
|
||||
throw error
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -273,29 +266,30 @@ export default {
|
||||
JoinGroup: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
const { groupId, userId } = params
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const joinGroupCypher = `
|
||||
MATCH (user:User {id: $userId}), (group:Group {id: $groupId})
|
||||
MERGE (user)-[membership:MEMBER_OF]->(group)
|
||||
ON CREATE SET
|
||||
membership.createdAt = toString(datetime()),
|
||||
membership.updatedAt = null,
|
||||
membership.role =
|
||||
CASE WHEN group.groupType = 'public'
|
||||
THEN 'usual'
|
||||
ELSE 'pending'
|
||||
END
|
||||
RETURN user {.*}, membership {.*}
|
||||
`
|
||||
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
|
||||
return transactionResponse.records.map((record) => {
|
||||
return { user: record.get('user'), membership: record.get('membership') }
|
||||
})
|
||||
})
|
||||
try {
|
||||
return (await writeTxResultPromise)[0]
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
const result = await session.writeTransaction(async (transaction) => {
|
||||
const joinGroupCypher = `
|
||||
MATCH (user:User {id: $userId}), (group:Group {id: $groupId})
|
||||
MERGE (user)-[membership:MEMBER_OF]->(group)
|
||||
ON CREATE SET
|
||||
membership.createdAt = toString(datetime()),
|
||||
membership.updatedAt = null,
|
||||
membership.role =
|
||||
CASE WHEN group.groupType = 'public'
|
||||
THEN 'usual'
|
||||
ELSE 'pending'
|
||||
END
|
||||
RETURN user {.*}, membership {.*}
|
||||
`
|
||||
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
|
||||
return transactionResponse.records.map((record) => {
|
||||
return { user: record.get('user'), membership: record.get('membership') }
|
||||
})
|
||||
})
|
||||
if (!result[0]) {
|
||||
throw new UserInputError('Could not find User or Group')
|
||||
}
|
||||
return result[0]
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -305,8 +299,6 @@ export default {
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -314,49 +306,46 @@ export default {
|
||||
ChangeGroupMemberRole: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
const { groupId, userId, roleInGroup } = params
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
let postRestrictionCypher = ''
|
||||
if (['usual', 'admin', 'owner'].includes(roleInGroup)) {
|
||||
postRestrictionCypher = `
|
||||
WITH group, member, membership
|
||||
FOREACH (restriction IN [(member)-[r:CANNOT_SEE]->(:Post)-[:IN]->(group) | r] |
|
||||
DELETE restriction)`
|
||||
} else {
|
||||
postRestrictionCypher = `
|
||||
WITH group, member, membership
|
||||
FOREACH (post IN [(p:Post)-[:IN]->(group) | p] |
|
||||
MERGE (member)-[:CANNOT_SEE]->(post))`
|
||||
}
|
||||
|
||||
const joinGroupCypher = `
|
||||
MATCH (member:User {id: $userId})
|
||||
MATCH (group:Group {id: $groupId})
|
||||
MERGE (member)-[membership:MEMBER_OF]->(group)
|
||||
ON CREATE SET
|
||||
membership.createdAt = toString(datetime()),
|
||||
membership.updatedAt = null,
|
||||
membership.role = $roleInGroup
|
||||
ON MATCH SET
|
||||
membership.updatedAt = toString(datetime()),
|
||||
membership.role = $roleInGroup
|
||||
${postRestrictionCypher}
|
||||
RETURN member {.*} as user, membership {.*}
|
||||
`
|
||||
|
||||
const transactionResponse = await transaction.run(joinGroupCypher, {
|
||||
groupId,
|
||||
userId,
|
||||
roleInGroup,
|
||||
})
|
||||
const [member] = transactionResponse.records.map((record) => {
|
||||
return { user: record.get('user'), membership: record.get('membership') }
|
||||
})
|
||||
return member
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
let postRestrictionCypher = ''
|
||||
if (['usual', 'admin', 'owner'].includes(roleInGroup)) {
|
||||
postRestrictionCypher = `
|
||||
WITH group, member, membership
|
||||
FOREACH (restriction IN [(member)-[r:CANNOT_SEE]->(:Post)-[:IN]->(group) | r] |
|
||||
DELETE restriction)`
|
||||
} else {
|
||||
postRestrictionCypher = `
|
||||
With group, member, membership
|
||||
FOREACH (post IN [(p:Post)-[:IN]->(group) | p] |
|
||||
MERGE (member)-[:CANNOT_SEE]->(post))`
|
||||
}
|
||||
|
||||
const joinGroupCypher = `
|
||||
MATCH (member:User {id: $userId})
|
||||
MATCH (group:Group {id: $groupId})
|
||||
MERGE (member)-[membership:MEMBER_OF]->(group)
|
||||
ON CREATE SET
|
||||
membership.createdAt = toString(datetime()),
|
||||
membership.updatedAt = null,
|
||||
membership.role = $roleInGroup
|
||||
ON MATCH SET
|
||||
membership.updatedAt = toString(datetime()),
|
||||
membership.role = $roleInGroup
|
||||
${postRestrictionCypher}
|
||||
RETURN member {.*} as user, membership {.*}
|
||||
`
|
||||
|
||||
const transactionResponse = await transaction.run(joinGroupCypher, {
|
||||
groupId,
|
||||
userId,
|
||||
roleInGroup,
|
||||
})
|
||||
const [member] = transactionResponse.records.map((record) => {
|
||||
return { user: record.get('user'), membership: record.get('membership') }
|
||||
})
|
||||
return member
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -366,8 +355,6 @@ export default {
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -379,30 +366,24 @@ export default {
|
||||
const { groupId } = params
|
||||
const userId = context.user.id
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (group:Group { id: $groupId })
|
||||
MATCH (user:User { id: $userId })
|
||||
MERGE (user)-[m:MUTED]->(group)
|
||||
SET m.createdAt = toString(datetime())
|
||||
RETURN group { .* }
|
||||
`,
|
||||
{
|
||||
groupId,
|
||||
userId,
|
||||
},
|
||||
)
|
||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||
return group
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (group:Group { id: $groupId })
|
||||
MATCH (user:User { id: $userId })
|
||||
MERGE (user)-[m:MUTED]->(group)
|
||||
SET m.createdAt = toString(datetime())
|
||||
RETURN group { .* }
|
||||
`,
|
||||
{
|
||||
groupId,
|
||||
userId,
|
||||
},
|
||||
)
|
||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||
return group
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -414,27 +395,24 @@ export default {
|
||||
const { groupId } = params
|
||||
const userId = context.user.id
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (group:Group { id: $groupId })
|
||||
MATCH (user:User { id: $userId })
|
||||
OPTIONAL MATCH (user)-[m:MUTED]->(group)
|
||||
DELETE m
|
||||
RETURN group { .* }
|
||||
`,
|
||||
{
|
||||
groupId,
|
||||
userId,
|
||||
},
|
||||
)
|
||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||
return group
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (group:Group { id: $groupId })
|
||||
MATCH (user:User { id: $userId })
|
||||
OPTIONAL MATCH (user)-[m:MUTED]->(group)
|
||||
DELETE m
|
||||
RETURN group { .* }
|
||||
`,
|
||||
{
|
||||
groupId,
|
||||
userId,
|
||||
},
|
||||
)
|
||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||
return group
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -540,9 +518,12 @@ const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId)
|
||||
groupId,
|
||||
userId,
|
||||
})
|
||||
const [user] = await transactionResponse.records.map((record) => {
|
||||
const [result] = transactionResponse.records.map((record) => {
|
||||
return { user: record.get('user'), membership: record.get('membership') }
|
||||
})
|
||||
return user
|
||||
if (!result) {
|
||||
throw new UserInputError('User is not a member of this group')
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
@ -33,20 +33,19 @@ export default function Resolver(type, options: any = {}) {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const id = parent[idAttribute]
|
||||
const session = driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const cypher = `
|
||||
MATCH(:${type} {${idAttribute}: $id})${connection}
|
||||
RETURN related {.*} as related
|
||||
`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
return result.records.map((r) => r.get('related'))
|
||||
})
|
||||
try {
|
||||
let response = await readTxResultPromise
|
||||
let response = await session.readTransaction(async (txc) => {
|
||||
const cypher = `
|
||||
MATCH(:${type} {${idAttribute}: $id})${connection}
|
||||
RETURN related {.*} as related
|
||||
`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
return result.records.map((r) => r.get('related'))
|
||||
})
|
||||
if (returnType === 'object') response = response[0] || null
|
||||
return response
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,17 +58,16 @@ export default function Resolver(type, options: any = {}) {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const id = parent[idAttribute]
|
||||
const session = driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const nodeCondition = condition.replace('this', 'this {id: $id}')
|
||||
const cypher = `${nodeCondition} as ${key}`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
const [response] = result.records.map((r) => r.get(key))
|
||||
return response
|
||||
})
|
||||
try {
|
||||
return await readTxResultPromise
|
||||
return await session.readTransaction(async (txc) => {
|
||||
const nodeCondition = condition.replace('this', 'this {id: $id}')
|
||||
const cypher = `${nodeCondition} as ${key}`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
const [response] = result.records.map((r) => r.get(key))
|
||||
return response
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,20 +80,19 @@ export default function Resolver(type, options: any = {}) {
|
||||
resolvers[key] = async (parent, _params, { driver, cypherParams }, _resolveInfo) => {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const session = driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const id = parent[idAttribute]
|
||||
const cypher = `
|
||||
MATCH(u:${type} {${idAttribute}: $id})${connection}
|
||||
RETURN COUNT(DISTINCT(related)) as count
|
||||
`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
const [response] = result.records.map((r) => r.get('count').toNumber())
|
||||
return response
|
||||
})
|
||||
try {
|
||||
return await readTxResultPromise
|
||||
return await session.readTransaction(async (txc) => {
|
||||
const id = parent[idAttribute]
|
||||
const cypher = `
|
||||
MATCH(u:${type} {${idAttribute}: $id})${connection}
|
||||
RETURN COUNT(DISTINCT(related)) as count
|
||||
`
|
||||
const result = await txc.run(cypher, { id, cypherParams })
|
||||
const [response] = result.records.map((r) => r.get('count').toNumber())
|
||||
return response
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ export default async function alreadyExistingMail({ args, context }) {
|
||||
args.email = normalizeEmail(args.email)
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
const existingEmailAddressTxPromise = session.writeTransaction(async (transaction) => {
|
||||
const result = await session.readTransaction(async (transaction) => {
|
||||
const existingEmailAddressTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (email:EmailAddress {email: $email})
|
||||
@ -24,13 +24,13 @@ export default async function alreadyExistingMail({ args, context }) {
|
||||
}
|
||||
})
|
||||
})
|
||||
const [emailBelongsToUser] = await existingEmailAddressTxPromise
|
||||
const [emailBelongsToUser] = result
|
||||
/*
|
||||
const { alreadyExistingEmail, user } =
|
||||
if (user) throw new UserInputError('A user account with this email already exists.')
|
||||
*/
|
||||
return emailBelongsToUser || {}
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,29 +6,29 @@ import { mergeWith, isArray } from 'lodash'
|
||||
|
||||
const getInvisiblePosts = async (context) => {
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = await session.readTransaction(async (transaction) => {
|
||||
let cypher = ''
|
||||
const { user } = context
|
||||
if (user?.id) {
|
||||
cypher = `
|
||||
MATCH (post:Post)<-[:CANNOT_SEE]-(user:User { id: $userId })
|
||||
RETURN collect(post.id) AS invisiblePostIds`
|
||||
} else {
|
||||
cypher = `
|
||||
MATCH (post:Post)-[:IN]->(group:Group)
|
||||
WHERE NOT group.groupType = 'public'
|
||||
RETURN collect(post.id) AS invisiblePostIds`
|
||||
}
|
||||
const invisiblePostIdsResponse = await transaction.run(cypher, {
|
||||
userId: user ? user.id : null,
|
||||
})
|
||||
return invisiblePostIdsResponse.records.map((record) => record.get('invisiblePostIds'))
|
||||
})
|
||||
try {
|
||||
const [invisiblePostIds] = readTxResultPromise
|
||||
const readTxResult = await session.readTransaction(async (transaction) => {
|
||||
let cypher = ''
|
||||
const { user } = context
|
||||
if (user?.id) {
|
||||
cypher = `
|
||||
MATCH (post:Post)<-[:CANNOT_SEE]-(user:User { id: $userId })
|
||||
RETURN collect(post.id) AS invisiblePostIds`
|
||||
} else {
|
||||
cypher = `
|
||||
MATCH (post:Post)-[:IN]->(group:Group)
|
||||
WHERE NOT group.groupType = 'public'
|
||||
RETURN collect(post.id) AS invisiblePostIds`
|
||||
}
|
||||
const invisiblePostIdsResponse = await transaction.run(cypher, {
|
||||
userId: user ? user.id : null,
|
||||
})
|
||||
return invisiblePostIdsResponse.records.map((record) => record.get('invisiblePostIds'))
|
||||
})
|
||||
const [invisiblePostIds] = readTxResult
|
||||
return invisiblePostIds
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,19 +9,19 @@ const getMyGroupIds = async (context) => {
|
||||
if (!user?.id) return []
|
||||
const session = context.driver.session()
|
||||
|
||||
const readTxResultPromise = await session.readTransaction(async (transaction) => {
|
||||
const cypher = `
|
||||
MATCH (group:Group)<-[membership:MEMBER_OF]-(:User { id: $userId })
|
||||
WHERE membership.role IN ['usual', 'admin', 'owner']
|
||||
RETURN collect(group.id) AS myGroupIds`
|
||||
const getMyGroupIdsResponse = await transaction.run(cypher, { userId: user.id })
|
||||
return getMyGroupIdsResponse.records.map((record) => record.get('myGroupIds'))
|
||||
})
|
||||
try {
|
||||
const [myGroupIds] = readTxResultPromise
|
||||
const readTxResult = await session.readTransaction(async (transaction) => {
|
||||
const cypher = `
|
||||
MATCH (group:Group)<-[membership:MEMBER_OF]-(:User { id: $userId })
|
||||
WHERE membership.role IN ['usual', 'admin', 'owner']
|
||||
RETURN collect(group.id) AS myGroupIds`
|
||||
const getMyGroupIdsResponse = await transaction.run(cypher, { userId: user.id })
|
||||
return getMyGroupIdsResponse.records.map((record) => record.get('myGroupIds'))
|
||||
})
|
||||
const [myGroupIds] = readTxResult
|
||||
return myGroupIds
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ export default {
|
||||
await setMessagesAsDistributed(undistributedMessagesIds, session)
|
||||
}
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
// send subscription to author to updated the messages
|
||||
}
|
||||
@ -82,43 +82,41 @@ export default {
|
||||
const session = context.driver.session()
|
||||
|
||||
try {
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const createMessageCypher = `
|
||||
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
||||
OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image)
|
||||
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)
|
||||
OPTIONAL MATCH (room)<-[:CHATS_IN]-(recipientUser:User)
|
||||
WHERE NOT recipientUser.id = $currentUserId
|
||||
WITH MAX(m.indexId) as maxIndex, room, currentUser, image, recipientUser
|
||||
CREATE (currentUser)-[:CREATED]->(message:Message {
|
||||
createdAt: toString(datetime()),
|
||||
id: apoc.create.uuid(),
|
||||
indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END,
|
||||
content: LEFT($content,2000),
|
||||
saved: true,
|
||||
distributed: false,
|
||||
seen: false
|
||||
})-[:INSIDE]->(room)
|
||||
SET room.lastMessageAt = toString(datetime())
|
||||
RETURN message {
|
||||
.*,
|
||||
indexId: toString(message.indexId),
|
||||
recipientId: recipientUser.id,
|
||||
senderId: currentUser.id,
|
||||
username: currentUser.name,
|
||||
avatar: image.url,
|
||||
date: message.createdAt
|
||||
}
|
||||
`
|
||||
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
||||
OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image)
|
||||
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)
|
||||
OPTIONAL MATCH (room)<-[:CHATS_IN]-(recipientUser:User)
|
||||
WHERE NOT recipientUser.id = $currentUserId
|
||||
WITH MAX(m.indexId) as maxIndex, room, currentUser, image, recipientUser
|
||||
CREATE (currentUser)-[:CREATED]->(message:Message {
|
||||
createdAt: toString(datetime()),
|
||||
id: apoc.create.uuid(),
|
||||
indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END,
|
||||
content: LEFT($content,2000),
|
||||
saved: true,
|
||||
distributed: false,
|
||||
seen: false
|
||||
})-[:INSIDE]->(room)
|
||||
SET room.lastMessageAt = toString(datetime())
|
||||
RETURN message {
|
||||
.*,
|
||||
indexId: toString(message.indexId),
|
||||
recipientId: recipientUser.id,
|
||||
senderId: currentUser.id,
|
||||
username: currentUser.name,
|
||||
avatar: image.url,
|
||||
date: message.createdAt
|
||||
}
|
||||
`
|
||||
const createMessageTxResponse = await transaction.run(createMessageCypher, {
|
||||
currentUserId,
|
||||
roomId,
|
||||
content,
|
||||
})
|
||||
|
||||
const [message] = await createMessageTxResponse.records.map((record) =>
|
||||
record.get('message'),
|
||||
)
|
||||
const [message] = createMessageTxResponse.records.map((record) => record.get('message'))
|
||||
|
||||
// this is the case if the room doesn't exist - requires refactoring for implicit rooms
|
||||
if (!message) {
|
||||
@ -142,38 +140,32 @@ export default {
|
||||
|
||||
return { ...message, files: atns }
|
||||
})
|
||||
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
MarkMessagesAsSeen: async (_parent, params, context, _resolveInfo) => {
|
||||
const { messageIds } = params
|
||||
const currentUserId = context.user.id
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const setSeenCypher = `
|
||||
MATCH (m:Message)<-[:CREATED]-(user:User)
|
||||
WHERE m.id IN $messageIds AND NOT user.id = $currentUserId
|
||||
SET m.seen = true
|
||||
RETURN m { .* }
|
||||
`
|
||||
const setSeenTxResponse = await transaction.run(setSeenCypher, {
|
||||
messageIds,
|
||||
currentUserId,
|
||||
})
|
||||
const messages = await setSeenTxResponse.records.map((record) => record.get('m'))
|
||||
return messages
|
||||
})
|
||||
try {
|
||||
await writeTxResultPromise
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
const setSeenCypher = `
|
||||
MATCH (m:Message)<-[:CREATED]-(user:User)
|
||||
WHERE m.id IN $messageIds AND NOT user.id = $currentUserId
|
||||
SET m.seen = true
|
||||
RETURN m { .* }
|
||||
`
|
||||
const setSeenTxResponse = await transaction.run(setSeenCypher, {
|
||||
messageIds,
|
||||
currentUserId,
|
||||
})
|
||||
return setSeenTxResponse.records.map((record) => record.get('m'))
|
||||
})
|
||||
// send subscription to author to updated the messages
|
||||
return true
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -34,7 +34,7 @@ export default {
|
||||
const [reviewed] = await reviewWriteTxResultPromise
|
||||
return reviewed || null
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -80,7 +80,7 @@ export default {
|
||||
const notifications = await readTxResultPromise
|
||||
return notifications
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -111,7 +111,7 @@ export default {
|
||||
const [notifications] = await writeTxResultPromise
|
||||
return notifications
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
markAllAsRead: async (parent, args, context, _resolveInfo) => {
|
||||
@ -140,7 +140,7 @@ export default {
|
||||
const notifications = await writeTxResultPromise
|
||||
return notifications
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -54,7 +54,7 @@ export default {
|
||||
const [reset] = await passwordResetTxPromise
|
||||
return !!reset?.properties.usedAt
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -88,7 +88,7 @@ export default {
|
||||
const [emotionsCount] = await readTxResultPromise
|
||||
return emotionsCount
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
PostsEmotionsByCurrentUser: async (_object, params, context: Context, _resolveInfo) => {
|
||||
@ -364,7 +364,7 @@ export default {
|
||||
const [emoted] = await writeTxResultPromise
|
||||
return emoted
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
pinPost: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
@ -464,7 +464,7 @@ export default {
|
||||
try {
|
||||
;[unpinnedPost] = await writeTxResultPromise
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
return unpinnedPost
|
||||
},
|
||||
@ -552,7 +552,7 @@ export default {
|
||||
post.viewedTeaserCount = post.viewedTeaserCount.low
|
||||
return post
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
toggleObservePost: async (_parent, params, context, _resolveInfo) => {
|
||||
@ -581,7 +581,7 @@ export default {
|
||||
post.viewedTeaserCount = post.viewedTeaserCount.low
|
||||
return post
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
pushPost: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
@ -705,7 +705,7 @@ export default {
|
||||
const relatedContributions = await writeTxResultPromise
|
||||
return relatedContributions
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -37,7 +37,7 @@ export default {
|
||||
const [filedReport] = await fileReportWriteTxResultPromise
|
||||
return filedReport || null
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -108,7 +108,7 @@ export default {
|
||||
const reports = await reportsReadTxPromise
|
||||
return reports || []
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -143,7 +143,7 @@ export default {
|
||||
return relationshipWithNestedAttributes
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
return filed
|
||||
}, */
|
||||
@ -177,7 +177,7 @@ export default {
|
||||
return relationshipWithNestedAttributes
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
return reviewed
|
||||
},
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
@ -53,7 +52,7 @@ export default {
|
||||
const count = await getUnreadRoomsCount(currentUserId, session)
|
||||
return count
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -67,43 +66,40 @@ export default {
|
||||
throw new Error('Cannot create a room with self')
|
||||
}
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const createRoomCypher = `
|
||||
MATCH (currentUser:User { id: $currentUserId })
|
||||
MATCH (user:User { id: $userId })
|
||||
MERGE (currentUser)-[:CHATS_IN]->(room:Room)<-[:CHATS_IN]-(user)
|
||||
ON CREATE SET
|
||||
room.createdAt = toString(datetime()),
|
||||
room.id = apoc.create.uuid()
|
||||
WITH room, user, currentUser
|
||||
OPTIONAL MATCH (room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User)
|
||||
WHERE NOT sender.id = $currentUserId AND NOT message.seen
|
||||
WITH room, user, currentUser, message,
|
||||
user.name AS roomName
|
||||
RETURN room {
|
||||
.*,
|
||||
users: [properties(currentUser), properties(user)],
|
||||
roomName: roomName,
|
||||
unreadCount: toString(COUNT(DISTINCT message))
|
||||
}
|
||||
`
|
||||
const createRommTxResponse = await transaction.run(createRoomCypher, {
|
||||
userId,
|
||||
currentUserId,
|
||||
})
|
||||
const [room] = await createRommTxResponse.records.map((record) => record.get('room'))
|
||||
return room
|
||||
})
|
||||
try {
|
||||
const room = await writeTxResultPromise
|
||||
const room = await session.writeTransaction(async (transaction) => {
|
||||
const createRoomCypher = `
|
||||
MATCH (currentUser:User { id: $currentUserId })
|
||||
MATCH (user:User { id: $userId })
|
||||
MERGE (currentUser)-[:CHATS_IN]->(room:Room)<-[:CHATS_IN]-(user)
|
||||
ON CREATE SET
|
||||
room.createdAt = toString(datetime()),
|
||||
room.id = apoc.create.uuid()
|
||||
WITH room, user, currentUser
|
||||
OPTIONAL MATCH (room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User)
|
||||
WHERE NOT sender.id = $currentUserId AND NOT message.seen
|
||||
WITH room, user, currentUser, message,
|
||||
user.name AS roomName
|
||||
RETURN room {
|
||||
.*,
|
||||
users: [properties(currentUser), properties(user)],
|
||||
roomName: roomName,
|
||||
unreadCount: toString(COUNT(DISTINCT message))
|
||||
}
|
||||
`
|
||||
const createRoomTxResponse = await transaction.run(createRoomCypher, {
|
||||
userId,
|
||||
currentUserId,
|
||||
})
|
||||
const [room] = createRoomTxResponse.records.map((record) => record.get('room'))
|
||||
return room
|
||||
})
|
||||
if (room) {
|
||||
room.roomId = room.id
|
||||
}
|
||||
return room
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -134,7 +134,7 @@ const getSearchResults = async (context, setup, params, resultCallback = searchR
|
||||
const results = await searchResultPromise(session, setup, params)
|
||||
return resultCallback(results)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ export default {
|
||||
const [isShouted] = await shoutWriteTxResultPromise
|
||||
return isShouted
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
|
||||
@ -55,7 +55,7 @@ export default {
|
||||
const [isShouted] = await unshoutWriteTxResultPromise
|
||||
return isShouted
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
@ -39,21 +38,16 @@ export default {
|
||||
})
|
||||
} AS result`
|
||||
const session = context.driver.session()
|
||||
const resultPromise = session.readTransaction(async (transaction) => {
|
||||
const transactionResponse = transaction.run(cypher, {
|
||||
id,
|
||||
})
|
||||
return transactionResponse
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await resultPromise
|
||||
const result = await session.readTransaction(async (transaction) => {
|
||||
return await transaction.run(cypher, { id })
|
||||
})
|
||||
const userData = result.records[0].get('result')
|
||||
userData.posts.sort(byCreationDate)
|
||||
userData.posts.forEach((post) => post.comments.sort(byCreationDate))
|
||||
return userData
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -1066,7 +1066,7 @@ describe('setTrophyBadgeSelected', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
expect.objectContaining({
|
||||
message: 'Error: You cannot set badges not rewarded to you.',
|
||||
message: 'You cannot set badges not rewarded to you.',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@ -1083,7 +1083,7 @@ describe('setTrophyBadgeSelected', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
expect.objectContaining({
|
||||
message: 'Error: You cannot set badges not rewarded to you.',
|
||||
message: 'You cannot set badges not rewarded to you.',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@ -49,21 +49,19 @@ export default {
|
||||
User: async (object, args, context, resolveInfo) => {
|
||||
if (args.email) {
|
||||
args.email = normalizeEmail(args.email)
|
||||
let session
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
session = context.driver.session()
|
||||
const readTxResult = await session.readTransaction((txc) => {
|
||||
const result = txc.run(
|
||||
return txc.run(
|
||||
`
|
||||
MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $args.email})
|
||||
RETURN user {.*, email: e.email}`,
|
||||
MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $args.email})
|
||||
RETURN user {.*, email: e.email}`,
|
||||
{ args },
|
||||
)
|
||||
return result
|
||||
})
|
||||
return readTxResult.records.map((r) => r.get('user'))
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
return neo4jgraphql(object, args, context, resolveInfo)
|
||||
@ -105,28 +103,27 @@ export default {
|
||||
if (currentUser.id === args.id) return null
|
||||
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const unBlockUserTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (blockedUser:User {id: $args.id})
|
||||
MATCH (currentUser:User {id: $currentUser.id})
|
||||
OPTIONAL MATCH (currentUser)-[r:FOLLOWS]->(blockedUser)
|
||||
DELETE r
|
||||
CREATE (currentUser)-[:BLOCKED]->(blockedUser)
|
||||
RETURN blockedUser {.*}
|
||||
`,
|
||||
{ currentUser, args },
|
||||
)
|
||||
return unBlockUserTransactionResponse.records.map((record) => record.get('blockedUser'))[0]
|
||||
})
|
||||
try {
|
||||
const blockedUser = await writeTxResultPromise
|
||||
const blockedUser = await session.writeTransaction(async (transaction) => {
|
||||
const blockUserResponse = await transaction.run(
|
||||
`
|
||||
MATCH (blockedUser:User {id: $args.id})
|
||||
MATCH (currentUser:User {id: $currentUser.id})
|
||||
OPTIONAL MATCH (currentUser)-[r:FOLLOWS]->(blockedUser)
|
||||
DELETE r
|
||||
MERGE (currentUser)-[:BLOCKED]->(blockedUser)
|
||||
RETURN blockedUser {.*}
|
||||
`,
|
||||
{ currentUser, args },
|
||||
)
|
||||
return blockUserResponse.records.map((record) => record.get('blockedUser'))[0]
|
||||
})
|
||||
if (!blockedUser) {
|
||||
throw new UserInputError('Could not find User')
|
||||
}
|
||||
return blockedUser
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
unblockUser: async (_object, args, context, _resolveInfo) => {
|
||||
@ -134,25 +131,22 @@ export default {
|
||||
if (currentUser.id === args.id) return null
|
||||
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const unBlockUserTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(blockedUser:User {id: $args.id})
|
||||
DELETE r
|
||||
RETURN blockedUser {.*}
|
||||
`,
|
||||
{ currentUser, args },
|
||||
)
|
||||
return unBlockUserTransactionResponse.records.map((record) => record.get('blockedUser'))[0]
|
||||
})
|
||||
try {
|
||||
const unblockedUser = await writeTxResultPromise
|
||||
const unblockedUser = await session.writeTransaction(async (transaction) => {
|
||||
const unblockUserResponse = await transaction.run(
|
||||
`
|
||||
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(blockedUser:User {id: $args.id})
|
||||
DELETE r
|
||||
RETURN blockedUser {.*}
|
||||
`,
|
||||
{ currentUser, args },
|
||||
)
|
||||
return unblockUserResponse.records.map((record) => record.get('blockedUser'))[0]
|
||||
})
|
||||
if (!unblockedUser) {
|
||||
throw new Error('Could not find blocked User')
|
||||
throw new UserInputError('Could not find blocked User')
|
||||
}
|
||||
return unblockedUser
|
||||
} catch {
|
||||
throw new UserInputError('Could not find blocked User')
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -183,103 +177,100 @@ export default {
|
||||
}
|
||||
|
||||
const session = context.driver.session()
|
||||
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const updateUserTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $params.id})
|
||||
SET user += $params
|
||||
SET user.updatedAt = toString(datetime())
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{ params },
|
||||
)
|
||||
const [user] = updateUserTransactionResponse.records.map((record) => record.get('user'))
|
||||
if (avatarInput) {
|
||||
await images(context.config).mergeImage(user, 'AVATAR_IMAGE', avatarInput, {
|
||||
transaction,
|
||||
})
|
||||
}
|
||||
return user
|
||||
})
|
||||
try {
|
||||
const user = await writeTxResultPromise
|
||||
const user = await session.writeTransaction(async (transaction) => {
|
||||
const updateUserTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $params.id})
|
||||
SET user += $params
|
||||
SET user.updatedAt = toString(datetime())
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{ params },
|
||||
)
|
||||
const [user] = updateUserTransactionResponse.records.map((record) => record.get('user'))
|
||||
if (avatarInput) {
|
||||
await images(context.config).mergeImage(user, 'AVATAR_IMAGE', avatarInput, {
|
||||
transaction,
|
||||
})
|
||||
}
|
||||
return user
|
||||
})
|
||||
// TODO: put in a middleware, see "CreateGroup", "UpdateGroup"
|
||||
await createOrUpdateLocations('User', params.id, params.locationName, session, context)
|
||||
return user
|
||||
} catch (error) {
|
||||
throw new UserInputError(error.message)
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
DeleteUser: async (_object, params, context: Context, _resolveInfo) => {
|
||||
const { resource, id: userId } = params
|
||||
const allowedLabels = ['Post', 'Comment']
|
||||
const session = context.driver.session()
|
||||
|
||||
const deleteUserTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
if (resource?.length) {
|
||||
await Promise.all(
|
||||
resource.map(async (node) => {
|
||||
const txResult = await transaction.run(
|
||||
`
|
||||
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
||||
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
|
||||
SET resource.deleted = true
|
||||
SET resource.content = 'UNAVAILABLE'
|
||||
SET resource.contentExcerpt = 'UNAVAILABLE'
|
||||
SET resource.language = 'UNAVAILABLE'
|
||||
SET resource.createdAt = 'UNAVAILABLE'
|
||||
SET resource.updatedAt = 'UNAVAILABLE'
|
||||
SET comment.deleted = true
|
||||
RETURN resource {.*}
|
||||
`,
|
||||
{
|
||||
userId,
|
||||
},
|
||||
)
|
||||
return Promise.all(
|
||||
txResult.records
|
||||
.map((record) => record.get('resource'))
|
||||
.map((resource) =>
|
||||
images(context.config).deleteImage(resource, 'HERO_IMAGE', { transaction }),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const deleteUserTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})
|
||||
SET user.deleted = true
|
||||
SET user.name = 'UNAVAILABLE'
|
||||
SET user.about = 'UNAVAILABLE'
|
||||
SET user.lastActiveAt = 'UNAVAILABLE'
|
||||
SET user.createdAt = 'UNAVAILABLE'
|
||||
SET user.updatedAt = 'UNAVAILABLE'
|
||||
SET user.termsAndConditionsAgreedVersion = 'UNAVAILABLE'
|
||||
SET user.encryptedPassword = null
|
||||
WITH user
|
||||
OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress)
|
||||
DETACH DELETE email
|
||||
WITH user
|
||||
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
|
||||
DETACH DELETE socialMedia
|
||||
WITH user
|
||||
OPTIONAL MATCH (user)-[follow:FOLLOWS]-(:User)
|
||||
DELETE follow
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{ userId },
|
||||
)
|
||||
const [user] = deleteUserTransactionResponse.records.map((record) => record.get('user'))
|
||||
await images(context.config).deleteImage(user, 'AVATAR_IMAGE', { transaction })
|
||||
return user
|
||||
})
|
||||
try {
|
||||
const user = await deleteUserTxResultPromise
|
||||
return user
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
if (resource?.length) {
|
||||
await Promise.all(
|
||||
resource.map(async (node) => {
|
||||
if (!allowedLabels.includes(node)) {
|
||||
throw new UserInputError(`Invalid resource type: ${node}`)
|
||||
}
|
||||
const txResult = await transaction.run(
|
||||
`
|
||||
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
||||
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
|
||||
SET resource.deleted = true
|
||||
SET resource.content = 'UNAVAILABLE'
|
||||
SET resource.contentExcerpt = 'UNAVAILABLE'
|
||||
SET resource.language = 'UNAVAILABLE'
|
||||
SET resource.createdAt = 'UNAVAILABLE'
|
||||
SET resource.updatedAt = 'UNAVAILABLE'
|
||||
SET comment.deleted = true
|
||||
RETURN resource {.*}
|
||||
`,
|
||||
{
|
||||
userId,
|
||||
},
|
||||
)
|
||||
return Promise.all(
|
||||
txResult.records
|
||||
.map((record) => record.get('resource'))
|
||||
.map((resource) =>
|
||||
images(context.config).deleteImage(resource, 'HERO_IMAGE', { transaction }),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const deleteUserTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})
|
||||
SET user.deleted = true
|
||||
SET user.name = 'UNAVAILABLE'
|
||||
SET user.about = 'UNAVAILABLE'
|
||||
SET user.lastActiveAt = 'UNAVAILABLE'
|
||||
SET user.createdAt = 'UNAVAILABLE'
|
||||
SET user.updatedAt = 'UNAVAILABLE'
|
||||
SET user.termsAndConditionsAgreedVersion = 'UNAVAILABLE'
|
||||
SET user.encryptedPassword = null
|
||||
WITH user
|
||||
OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress)
|
||||
DETACH DELETE email
|
||||
WITH user
|
||||
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
|
||||
DETACH DELETE socialMedia
|
||||
WITH user
|
||||
OPTIONAL MATCH (user)-[follow:FOLLOWS]-(:User)
|
||||
DELETE follow
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{ userId },
|
||||
)
|
||||
const [user] = deleteUserTransactionResponse.records.map((record) => record.get('user'))
|
||||
await images(context.config).deleteImage(user, 'AVATAR_IMAGE', { transaction })
|
||||
return user
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
@ -289,24 +280,26 @@ export default {
|
||||
|
||||
if (context.user.id === id) throw new Error('you-cannot-change-your-own-role')
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const switchUserRoleResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $id})
|
||||
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(e:EmailAddress)
|
||||
SET user.role = $role
|
||||
SET user.updatedAt = toString(datetime())
|
||||
RETURN user {.*, email: e.email}
|
||||
`,
|
||||
{ id, role },
|
||||
)
|
||||
return switchUserRoleResponse.records.map((record) => record.get('user'))[0]
|
||||
})
|
||||
try {
|
||||
const user = await writeTxResultPromise
|
||||
const user = await session.writeTransaction(async (transaction) => {
|
||||
const switchUserRoleResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $id})
|
||||
OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(e:EmailAddress)
|
||||
SET user.role = $role
|
||||
SET user.updatedAt = toString(datetime())
|
||||
RETURN user {.*, email: e.email}
|
||||
`,
|
||||
{ id, role },
|
||||
)
|
||||
return switchUserRoleResponse.records.map((record) => record.get('user'))[0]
|
||||
})
|
||||
if (!user) {
|
||||
throw new UserInputError('Could not find User')
|
||||
}
|
||||
return user
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
saveCategorySettings: async (_object, args, context, _resolveInfo) => {
|
||||
@ -316,40 +309,30 @@ export default {
|
||||
} = context
|
||||
|
||||
const session = context.driver.session()
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(
|
||||
`
|
||||
MATCH (user:User { id: $id })-[previousCategories:NOT_INTERESTED_IN]->(category:Category)
|
||||
DELETE previousCategories
|
||||
RETURN user, category
|
||||
`,
|
||||
{ id },
|
||||
)
|
||||
})
|
||||
|
||||
// frontend gives [] when all categories are selected (default)
|
||||
if (activeCategories.length === 0) return true
|
||||
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const saveCategorySettingsResponse = await transaction.run(
|
||||
`
|
||||
MATCH (category:Category) WHERE NOT category.id IN $activeCategories
|
||||
MATCH (user:User { id: $id })
|
||||
MERGE (user)-[r:NOT_INTERESTED_IN]->(category)
|
||||
RETURN user, r, category
|
||||
`,
|
||||
{ id, activeCategories },
|
||||
)
|
||||
const [user] = await saveCategorySettingsResponse.records.map((record) =>
|
||||
record.get('user'),
|
||||
)
|
||||
return user
|
||||
})
|
||||
try {
|
||||
await writeTxResultPromise
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
await transaction.run(
|
||||
`
|
||||
MATCH (user:User { id: $id })-[previousCategories:NOT_INTERESTED_IN]->(:Category)
|
||||
DELETE previousCategories
|
||||
`,
|
||||
{ id },
|
||||
)
|
||||
// frontend gives [] when all categories are selected (default)
|
||||
if (activeCategories.length > 0) {
|
||||
await transaction.run(
|
||||
`
|
||||
MATCH (category:Category) WHERE NOT category.id IN $activeCategories
|
||||
MATCH (user:User { id: $id })
|
||||
MERGE (user)-[:NOT_INTERESTED_IN]->(category)
|
||||
`,
|
||||
{ id, activeCategories },
|
||||
)
|
||||
}
|
||||
})
|
||||
return true
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
updateOnlineStatus: async (_object, args, context: Context, _resolveInfo) => {
|
||||
@ -391,41 +374,37 @@ export default {
|
||||
}
|
||||
|
||||
const session = context.driver.session()
|
||||
|
||||
const query = session.writeTransaction(async (transaction) => {
|
||||
const queryBadge = `
|
||||
MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge {id: $badgeId})
|
||||
OPTIONAL MATCH (user)-[badgeRelation:SELECTED]->(badge)
|
||||
OPTIONAL MATCH (user)-[slotRelation:SELECTED{slot: $slot}]->(:Badge)
|
||||
DELETE badgeRelation, slotRelation
|
||||
MERGE (user)-[:SELECTED{slot: toInteger($slot)}]->(badge)
|
||||
RETURN user {.*}
|
||||
`
|
||||
const queryEmpty = `
|
||||
MATCH (user:User {id: $userId})
|
||||
OPTIONAL MATCH (user)-[slotRelation:SELECTED {slot: $slot}]->(:Badge)
|
||||
DELETE slotRelation
|
||||
RETURN user {.*}
|
||||
`
|
||||
const isDefault = !badgeId || badgeId === defaultTrophyBadge.id
|
||||
|
||||
const result = await transaction.run(isDefault ? queryEmpty : queryBadge, {
|
||||
userId,
|
||||
badgeId,
|
||||
slot,
|
||||
})
|
||||
return result.records.map((record) => record.get('user'))[0]
|
||||
})
|
||||
try {
|
||||
const user = await query
|
||||
const user = await session.writeTransaction(async (transaction) => {
|
||||
const queryBadge = `
|
||||
MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge {id: $badgeId})
|
||||
OPTIONAL MATCH (user)-[badgeRelation:SELECTED]->(badge)
|
||||
OPTIONAL MATCH (user)-[slotRelation:SELECTED{slot: $slot}]->(:Badge)
|
||||
DELETE badgeRelation, slotRelation
|
||||
MERGE (user)-[:SELECTED{slot: toInteger($slot)}]->(badge)
|
||||
RETURN user {.*}
|
||||
`
|
||||
const queryEmpty = `
|
||||
MATCH (user:User {id: $userId})
|
||||
OPTIONAL MATCH (user)-[slotRelation:SELECTED {slot: $slot}]->(:Badge)
|
||||
DELETE slotRelation
|
||||
RETURN user {.*}
|
||||
`
|
||||
const isDefault = !badgeId || badgeId === defaultTrophyBadge.id
|
||||
|
||||
const result = await transaction.run(isDefault ? queryEmpty : queryBadge, {
|
||||
userId,
|
||||
badgeId,
|
||||
slot,
|
||||
})
|
||||
return result.records.map((record) => record.get('user'))[0]
|
||||
})
|
||||
if (!user) {
|
||||
throw new Error('You cannot set badges not rewarded to you.')
|
||||
throw new UserInputError('You cannot set badges not rewarded to you.')
|
||||
}
|
||||
return user
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
resetTrophyBadgesSelected: async (_object, _args, context, _resolveInfo) => {
|
||||
@ -434,25 +413,21 @@ export default {
|
||||
} = context
|
||||
|
||||
const session = context.driver.session()
|
||||
|
||||
const query = session.writeTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})
|
||||
OPTIONAL MATCH (user)-[relation:SELECTED]->(:Badge)
|
||||
DELETE relation
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{ userId },
|
||||
)
|
||||
return result.records.map((record) => record.get('user'))[0]
|
||||
})
|
||||
try {
|
||||
return await query
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})
|
||||
OPTIONAL MATCH (user)-[relation:SELECTED]->(:Badge)
|
||||
DELETE relation
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{ userId },
|
||||
)
|
||||
return result.records.map((record) => record.get('user'))[0]
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -539,97 +514,80 @@ export default {
|
||||
},
|
||||
badgeTrophiesSelected: async (parent, _params, context, _resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
|
||||
const query = session.readTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})-[relation:SELECTED]->(badge:Badge)
|
||||
WITH relation, badge
|
||||
ORDER BY relation.slot ASC
|
||||
RETURN relation.slot as slot, badge {.*}
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return result.records
|
||||
})
|
||||
try {
|
||||
const badgesSelected = await query
|
||||
const badgesSelected = await session.readTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})-[relation:SELECTED]->(badge:Badge)
|
||||
WITH relation, badge
|
||||
ORDER BY relation.slot ASC
|
||||
RETURN relation.slot as slot, badge {.*}
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return result.records
|
||||
})
|
||||
const result = Array(TROPHY_BADGES_SELECTED_MAX).fill(defaultTrophyBadge)
|
||||
badgesSelected.map((record) => {
|
||||
badgesSelected.forEach((record) => {
|
||||
result[record.get('slot')] = record.get('badge')
|
||||
return true
|
||||
})
|
||||
return result
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
badgeTrophiesUnused: async (parent, _params, context, _resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
|
||||
const query = session.readTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
|
||||
WHERE NOT (user)-[:SELECTED]-(badge)
|
||||
RETURN badge {.*}
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return result.records.map((record) => record.get('badge'))
|
||||
})
|
||||
try {
|
||||
return await query
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.readTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
|
||||
WHERE NOT (user)-[:SELECTED]-(badge)
|
||||
RETURN badge {.*}
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return result.records.map((record) => record.get('badge'))
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
badgeTrophiesUnusedCount: async (parent, _params, context, _resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
|
||||
const query = session.readTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
|
||||
WHERE NOT (user)-[:SELECTED]-(badge)
|
||||
RETURN toString(COUNT(badge)) as count
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return result.records.map((record) => record.get('count'))[0]
|
||||
})
|
||||
try {
|
||||
return await query
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.readTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge)
|
||||
WHERE NOT (user)-[:SELECTED]-(badge)
|
||||
RETURN toString(COUNT(badge)) as count
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return result.records.map((record) => record.get('count'))[0]
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
badgeVerification: async (parent, _params, context, _resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
|
||||
const query = session.writeTransaction(async (transaction) => {
|
||||
const result = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})<-[:VERIFIES]-(verification:Badge)
|
||||
RETURN verification {.*}
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return result.records.map((record) => record.get('verification'))[0]
|
||||
})
|
||||
try {
|
||||
const result = await query
|
||||
const result = await session.readTransaction(async (transaction) => {
|
||||
const response = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $parent.id})<-[:VERIFIES]-(verification:Badge)
|
||||
RETURN verification {.*}
|
||||
`,
|
||||
{ parent },
|
||||
)
|
||||
return response.records.map((record) => record.get('verification'))[0]
|
||||
})
|
||||
return result ?? defaultVerificationBadge
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
...Resolver('User', {
|
||||
|
||||
@ -25,7 +25,7 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
||||
)
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -159,7 +159,7 @@ const postAuthorOfComment = async (commentId, { context }) => {
|
||||
})
|
||||
return postAuthorId.records.map((record) => record.get('authorId'))
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,21 +186,18 @@ const notifyFollowingUsers = async (postId, groupId, context) => {
|
||||
}
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
postId,
|
||||
reason,
|
||||
groupId: groupId || null,
|
||||
userId: context.user.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
postId,
|
||||
reason,
|
||||
groupId: groupId || null,
|
||||
userId: context.user.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,21 +229,18 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
|
||||
}
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
postId,
|
||||
reason,
|
||||
groupId,
|
||||
userId: context.user.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
postId,
|
||||
reason,
|
||||
groupId,
|
||||
userId: context.user.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,20 +261,17 @@ const notifyOwnersOfGroup = async (groupId, userId, reason, context) => {
|
||||
RETURN notification {.*, from: finalGroup, to: properties(owner), email: email, relatedUser: properties(user) }
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
groupId,
|
||||
reason,
|
||||
userId,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
groupId,
|
||||
reason,
|
||||
userId,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,21 +295,18 @@ const notifyMemberOfGroup = async (groupId, userId, reason, context) => {
|
||||
RETURN notification {.*, from: finalGroup, to: properties(user), email: email, relatedUser: properties(owner) }
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
groupId,
|
||||
reason,
|
||||
userId,
|
||||
ownerId: owner.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(cypher, {
|
||||
groupId,
|
||||
reason,
|
||||
userId,
|
||||
ownerId: owner.id,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,62 +364,58 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
RETURN notification {.*, from: finalResource, to: properties(user), email: email, relatedUser: properties(user) }
|
||||
`
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(mentionedCypher, {
|
||||
id,
|
||||
idsOfUsers,
|
||||
reason,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(mentionedCypher, {
|
||||
id,
|
||||
idsOfUsers,
|
||||
reason,
|
||||
})
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
||||
await validateNotifyUsers(label, reason)
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||
WHERE NOT (observingUser)-[:BLOCKED]-(commenter)
|
||||
AND NOT (observingUser)-[:MUTED]->(commenter)
|
||||
AND NOT observingUser.id = $userId
|
||||
OPTIONAL MATCH (observingUser)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WITH observingUser, emailAddress, post, comment, commenter
|
||||
MATCH (postAuthor:User)-[:WROTE]->(post)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, observingUser, emailAddress.email as email, post, commenter, postAuthor,
|
||||
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(observingUser),
|
||||
email: email,
|
||||
relatedUser: properties(commenter)
|
||||
}
|
||||
`,
|
||||
{
|
||||
commentId,
|
||||
reason,
|
||||
userId: context.user.id,
|
||||
},
|
||||
)
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
return await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||
WHERE NOT (observingUser)-[:BLOCKED]-(commenter)
|
||||
AND NOT (observingUser)-[:MUTED]->(commenter)
|
||||
AND NOT observingUser.id = $userId
|
||||
OPTIONAL MATCH (observingUser)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WITH observingUser, emailAddress, post, comment, commenter
|
||||
MATCH (postAuthor:User)-[:WROTE]->(post)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, observingUser, emailAddress.email as email, post, commenter, postAuthor,
|
||||
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(observingUser),
|
||||
email: email,
|
||||
relatedUser: properties(commenter)
|
||||
}
|
||||
`,
|
||||
{
|
||||
commentId,
|
||||
reason,
|
||||
userId: context.user.id,
|
||||
},
|
||||
)
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,30 +431,29 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
|
||||
|
||||
// Find Recipient
|
||||
const session = context.driver.session()
|
||||
const messageRecipient = session.readTransaction(async (transaction) => {
|
||||
const messageRecipientCypher = `
|
||||
MATCH (senderUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
||||
MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WHERE NOT recipientUser.id = $currentUserId
|
||||
AND NOT (recipientUser)-[:BLOCKED]-(senderUser)
|
||||
AND NOT (recipientUser)-[:MUTED]->(senderUser)
|
||||
RETURN senderUser {.*}, recipientUser {.*}, emailAddress {.email}
|
||||
`
|
||||
const txResponse = await transaction.run(messageRecipientCypher, {
|
||||
currentUserId,
|
||||
roomId,
|
||||
})
|
||||
|
||||
return {
|
||||
senderUser: await txResponse.records.map((record) => record.get('senderUser'))[0],
|
||||
recipientUser: await txResponse.records.map((record) => record.get('recipientUser'))[0],
|
||||
email: await txResponse.records.map((record) => record.get('emailAddress'))[0]?.email,
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
// Execute Query
|
||||
const { senderUser, recipientUser, email } = await messageRecipient
|
||||
const { senderUser, recipientUser, email } = await session.readTransaction(
|
||||
async (transaction) => {
|
||||
const messageRecipientCypher = `
|
||||
MATCH (senderUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
||||
MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WHERE NOT recipientUser.id = $currentUserId
|
||||
AND NOT (recipientUser)-[:BLOCKED]-(senderUser)
|
||||
AND NOT (recipientUser)-[:MUTED]->(senderUser)
|
||||
RETURN senderUser {.*}, recipientUser {.*}, emailAddress {.email}
|
||||
`
|
||||
const txResponse = await transaction.run(messageRecipientCypher, {
|
||||
currentUserId,
|
||||
roomId,
|
||||
})
|
||||
|
||||
return {
|
||||
senderUser: txResponse.records.map((record) => record.get('senderUser'))[0],
|
||||
recipientUser: txResponse.records.map((record) => record.get('recipientUser'))[0],
|
||||
email: txResponse.records.map((record) => record.get('emailAddress'))[0]?.email,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (recipientUser) {
|
||||
// send subscriptions
|
||||
@ -493,10 +476,8 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
|
||||
|
||||
// Return resolver result to client
|
||||
return message
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ const setPostCounter = async (postId, relation, context) => {
|
||||
return txc.run(createRelatedCypher(relation), { currentUser, postId })
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ const validateCreateComment = async (resolve, root, args, context, info) => {
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,28 +64,27 @@ const validateReview = async (resolve, root, args, context, info) => {
|
||||
const { user, driver } = context
|
||||
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
||||
const session = driver.session()
|
||||
const reportReadTxPromise = session.readTransaction(async (transaction) => {
|
||||
const validateReviewTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (resource {id: $resourceId})
|
||||
WHERE resource:User OR resource:Post OR resource:Comment
|
||||
OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource)
|
||||
OPTIONAL MATCH (resource)<-[:WROTE]-(author:User)
|
||||
RETURN [l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User']][0] AS label, author, filed
|
||||
`,
|
||||
{
|
||||
resourceId,
|
||||
submitterId: user.id,
|
||||
},
|
||||
)
|
||||
return validateReviewTransactionResponse.records.map((record) => ({
|
||||
label: record.get('label'),
|
||||
author: record.get('author'),
|
||||
filed: record.get('filed'),
|
||||
}))
|
||||
})
|
||||
try {
|
||||
const txResult = await reportReadTxPromise
|
||||
const txResult = await session.readTransaction(async (transaction) => {
|
||||
const validateReviewTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (resource {id: $resourceId})
|
||||
WHERE resource:User OR resource:Post OR resource:Comment
|
||||
OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource)
|
||||
OPTIONAL MATCH (resource)<-[:WROTE]-(author:User)
|
||||
RETURN [l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User']][0] AS label, author, filed
|
||||
`,
|
||||
{
|
||||
resourceId,
|
||||
submitterId: user.id,
|
||||
},
|
||||
)
|
||||
return validateReviewTransactionResponse.records.map((record) => ({
|
||||
label: record.get('label'),
|
||||
author: record.get('author'),
|
||||
filed: record.get('filed'),
|
||||
}))
|
||||
})
|
||||
existingReportedResource = txResult
|
||||
if (!existingReportedResource?.length)
|
||||
throw new Error(`Resource not found or is not a Post|Comment|User!`)
|
||||
@ -101,7 +100,7 @@ const validateReview = async (resolve, root, args, context, info) => {
|
||||
if (authorId && authorId === user.id)
|
||||
throw new Error(`You cannot review your own ${existingReportedResource.label}!`)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
|
||||
return resolve(root, args, context, info)
|
||||
|
||||
@ -15,6 +15,7 @@ import helmet from 'helmet'
|
||||
import CONFIG from './config'
|
||||
import { context, getContext } from './context'
|
||||
import schema from './graphql/schema'
|
||||
import logger from './logger'
|
||||
import middleware from './middleware'
|
||||
|
||||
import type { ApolloServerExpressConfig } from 'apollo-server-express'
|
||||
@ -24,8 +25,12 @@ const createServer = (options?: ApolloServerExpressConfig) => {
|
||||
context,
|
||||
schema: middleware(schema),
|
||||
subscriptions: {
|
||||
keepAlive: 10000,
|
||||
onConnect: (connectionParams) =>
|
||||
getContext()(connectionParams as { headers: { authorization?: string } }),
|
||||
onDisconnect: () => {
|
||||
logger.debug('WebSocket client disconnected')
|
||||
},
|
||||
},
|
||||
debug: !!CONFIG.DEBUG,
|
||||
uploads: false,
|
||||
|
||||
@ -5,11 +5,22 @@ import type { S3Config } from '@config/index'
|
||||
|
||||
import { FileUploadCallback, FileDeleteCallback } from './types'
|
||||
|
||||
export const s3Service = (config: S3Config, prefix: string) => {
|
||||
const { AWS_BUCKET: Bucket } = config
|
||||
let cachedClient: S3Client | null = null
|
||||
let cachedConfig: S3Config | null = null
|
||||
|
||||
const getS3Client = (config: S3Config): S3Client => {
|
||||
if (cachedClient) {
|
||||
if (
|
||||
cachedConfig?.AWS_ENDPOINT !== config.AWS_ENDPOINT ||
|
||||
cachedConfig?.AWS_ACCESS_KEY_ID !== config.AWS_ACCESS_KEY_ID ||
|
||||
cachedConfig?.AWS_SECRET_ACCESS_KEY !== config.AWS_SECRET_ACCESS_KEY
|
||||
) {
|
||||
throw new Error('S3Client singleton was created with different credentials')
|
||||
}
|
||||
return cachedClient
|
||||
}
|
||||
const { AWS_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = config
|
||||
const s3 = new S3Client({
|
||||
cachedClient = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: AWS_SECRET_ACCESS_KEY,
|
||||
@ -17,6 +28,13 @@ export const s3Service = (config: S3Config, prefix: string) => {
|
||||
endpoint: AWS_ENDPOINT,
|
||||
forcePathStyle: true,
|
||||
})
|
||||
cachedConfig = config
|
||||
return cachedClient
|
||||
}
|
||||
|
||||
export const s3Service = (config: S3Config, prefix: string) => {
|
||||
const { AWS_BUCKET: Bucket } = config
|
||||
const s3 = getS3Client(config)
|
||||
|
||||
const uploadFile: FileUploadCallback = async ({ createReadStream, uniqueFilename, mimetype }) => {
|
||||
const s3Location = prefix.length > 0 ? `${prefix}/${uniqueFilename}` : uniqueFilename
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user