mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-01-20 20:01:25 +00:00
This is a side quest of #8558. The motivation is to be able to do dependency injection in the tests without overwriting global data. I saw the first merge conflict from #8551 and voila: It seems @Mogge could have used this already. refactor: follow @Mogge's review See: https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8603#pullrequestreview-2880714796 refactor: better test helper methods wip: continue refactoring wip: continue posts continue wip: continue groups continue registration registration continue messages continue observeposts continue categories continue posts in groups continue invite codes refactor: continue notificationsMiddleware continue statistics spec followed-users online-status mentions-in-groups posts-in-groups email spec finish all tests improve typescript missed one test remove one more reference of CONFIG eliminate one more global import of CONFIG fix language spec test fix two more test suites refactor: completely mock out 3rd part API request refactor test fixed user_management spec fixed more locatoin specs install types for jsonwebtoken one more fetchmock fixed one more suite fix one more spec yet another spec fix spec delete whitespaces remove beforeAll that the same as the default fix merge conflict fix e2e test refactor: use single callback function for `context` setup refactor: display logs from backend during CI Because why not? fix seeds fix login refactor: one unnecessary naming refactor: better editor support refactor: fail early Interestingly, I've had to destructure `context.user` in order to make typescript happy. Weird. refactor: undo changes to workflows - no effect We're running in `--detached` mode on CI, so I guess we won't be able to see the logs anyways. refactor: remove fetch from context after review See: refactor: found an easier way for required props Co-authored-by: Max <maxharz@gmail.com> Co-authored-by: Ulf Gebhardt <ulf.gebhardt@webcraft-media.de>
620 lines
23 KiB
TypeScript
620 lines
23 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
import { UserInputError } from 'apollo-server'
|
|
import { isEmpty } from 'lodash'
|
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
|
import { v4 as uuid } from 'uuid'
|
|
|
|
import { Context } from '@src/context'
|
|
|
|
import { validateEventParams } from './helpers/events'
|
|
import { filterForMutedUsers } from './helpers/filterForMutedUsers'
|
|
import { filterInvisiblePosts } from './helpers/filterInvisiblePosts'
|
|
import { filterPostsOfMyGroups } from './helpers/filterPostsOfMyGroups'
|
|
import Resolver from './helpers/Resolver'
|
|
import { images } from './images/images'
|
|
import { createOrUpdateLocations } from './users/location'
|
|
|
|
const maintainPinnedPosts = (params) => {
|
|
const pinnedPostFilter = { pinned: true }
|
|
if (isEmpty(params.filter)) {
|
|
params.filter = { OR: [pinnedPostFilter, {}] }
|
|
} else {
|
|
params.filter = { OR: [pinnedPostFilter, { ...params.filter }] }
|
|
}
|
|
return params
|
|
}
|
|
|
|
const filterEventDates = (params) => {
|
|
if (params.filter?.eventStart_gte) {
|
|
const date = params.filter.eventStart_gte
|
|
delete params.filter.eventStart_gte
|
|
params.filter = { ...params.filter, OR: [{ eventStart_gte: date }, { eventEnd_gte: date }] }
|
|
}
|
|
return params
|
|
}
|
|
|
|
export default {
|
|
Query: {
|
|
Post: async (object, params, context: Context, resolveInfo) => {
|
|
params = await filterPostsOfMyGroups(params, context)
|
|
params = await filterInvisiblePosts(params, context)
|
|
params = await filterForMutedUsers(params, context)
|
|
params = filterEventDates(params)
|
|
params = await maintainPinnedPosts(params)
|
|
return neo4jgraphql(object, params, context, resolveInfo)
|
|
},
|
|
profilePagePosts: async (object, params, context, resolveInfo) => {
|
|
params = await filterPostsOfMyGroups(params, context)
|
|
params = await filterInvisiblePosts(params, context)
|
|
params = await filterForMutedUsers(params, context)
|
|
return neo4jgraphql(object, params, context, resolveInfo)
|
|
},
|
|
PostsEmotionsCountByEmotion: async (_object, params, context, _resolveInfo) => {
|
|
const { postId, data } = params
|
|
const session = context.driver.session()
|
|
const readTxResultPromise = session.readTransaction(async (transaction) => {
|
|
const emotionsCountTransactionResponse = await transaction.run(
|
|
`
|
|
MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
|
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
|
`,
|
|
{ postId, data },
|
|
)
|
|
return emotionsCountTransactionResponse.records.map(
|
|
(record) => record.get('emotionsCount').low,
|
|
)
|
|
})
|
|
try {
|
|
const [emotionsCount] = await readTxResultPromise
|
|
return emotionsCount
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
PostsEmotionsByCurrentUser: async (_object, params, context: Context, _resolveInfo) => {
|
|
const { postId } = params
|
|
const session = context.driver.session()
|
|
const readTxResultPromise = session.readTransaction(async (transaction) => {
|
|
if (!context.user) {
|
|
throw new Error('Missing authenticated user.')
|
|
}
|
|
const emotionsTransactionResponse = await transaction.run(
|
|
`
|
|
MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
|
RETURN collect(emoted.emotion) as emotion
|
|
`,
|
|
{ userId: context.user.id, postId },
|
|
)
|
|
return emotionsTransactionResponse.records.map((record) => record.get('emotion'))
|
|
})
|
|
try {
|
|
const [emotions] = await readTxResultPromise
|
|
return emotions
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
},
|
|
PostsPinnedCounts: async (_object, params, context: Context, _resolveInfo) => {
|
|
const { config } = context
|
|
const [postsPinnedCount] = (
|
|
await context.database.query({
|
|
query: 'MATCH (p:Post { pinned: true }) RETURN COUNT (p) AS count',
|
|
})
|
|
).records.map((r) => Number(r.get('count').toString()))
|
|
return {
|
|
maxPinnedPosts: config.MAX_PINNED_POSTS,
|
|
currentlyPinnedPosts: postsPinnedCount,
|
|
}
|
|
},
|
|
},
|
|
Mutation: {
|
|
CreatePost: async (_parent, params, context: Context, _resolveInfo) => {
|
|
const { user } = context
|
|
if (!user) {
|
|
throw new Error('Missing authenticated user.')
|
|
}
|
|
const { config } = context
|
|
const { categoryIds, groupId } = params
|
|
const { image: imageInput } = params
|
|
|
|
const locationName = validateEventParams(params)
|
|
|
|
delete params.categoryIds
|
|
delete params.image
|
|
delete params.groupId
|
|
params.id = params.id || uuid()
|
|
const session = context.driver.session()
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
let groupCypher = ''
|
|
if (groupId) {
|
|
groupCypher = `
|
|
WITH post MATCH (group:Group { id: $groupId })
|
|
MERGE (post)-[:IN]->(group)`
|
|
const groupTypeResponse = await transaction.run(
|
|
`
|
|
MATCH (group:Group { id: $groupId }) RETURN group.groupType AS groupType`,
|
|
{ groupId },
|
|
)
|
|
const [groupType] = groupTypeResponse.records.map((record) => record.get('groupType'))
|
|
if (groupType !== 'public')
|
|
groupCypher += `
|
|
WITH post, group
|
|
MATCH (user:User)-[membership:MEMBER_OF]->(group)
|
|
WHERE group.groupType IN ['closed', 'hidden']
|
|
AND membership.role IN ['usual', 'admin', 'owner']
|
|
WITH post, collect(user.id) AS userIds
|
|
OPTIONAL MATCH path =(restricted:User) WHERE NOT restricted.id IN userIds
|
|
FOREACH (user IN nodes(path) |
|
|
MERGE (user)-[:CANNOT_SEE]->(post)
|
|
)`
|
|
}
|
|
const categoriesCypher =
|
|
config.CATEGORIES_ACTIVE && categoryIds
|
|
? `WITH post
|
|
UNWIND $categoryIds AS categoryId
|
|
MATCH (category:Category {id: categoryId})
|
|
MERGE (post)-[:CATEGORIZED]->(category)`
|
|
: ''
|
|
const createPostTransactionResponse = await transaction.run(
|
|
`
|
|
CREATE (post:Post)
|
|
SET post += $params
|
|
SET post.createdAt = toString(datetime())
|
|
SET post.updatedAt = toString(datetime())
|
|
SET post.sortDate = toString(datetime())
|
|
SET post.clickedCount = 0
|
|
SET post.viewedTeaserCount = 0
|
|
SET post:${params.postType}
|
|
WITH post
|
|
MATCH (author:User {id: $userId})
|
|
MERGE (post)<-[:WROTE]-(author)
|
|
MERGE (post)<-[obs:OBSERVES]-(author)
|
|
SET obs.active = true
|
|
SET obs.createdAt = toString(datetime())
|
|
SET obs.updatedAt = toString(datetime())
|
|
${categoriesCypher}
|
|
${groupCypher}
|
|
RETURN post {.*, postType: [l IN labels(post) WHERE NOT l = 'Post'] }
|
|
`,
|
|
{ userId: user.id, categoryIds, groupId, params },
|
|
)
|
|
const [post] = createPostTransactionResponse.records.map((record) => record.get('post'))
|
|
if (imageInput) {
|
|
await images(context.config).mergeImage(post, 'HERO_IMAGE', imageInput, { transaction })
|
|
}
|
|
return post
|
|
})
|
|
try {
|
|
const post = await writeTxResultPromise
|
|
if (locationName) {
|
|
await createOrUpdateLocations('Post', post.id, locationName, session, context)
|
|
}
|
|
return post
|
|
} catch (e) {
|
|
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
|
throw new UserInputError('Post with this slug already exists!')
|
|
throw new Error(e)
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
},
|
|
UpdatePost: async (_parent, params, context: Context, _resolveInfo) => {
|
|
const { config } = context
|
|
const { categoryIds } = params
|
|
const { image: imageInput } = params
|
|
|
|
const locationName = validateEventParams(params)
|
|
|
|
delete params.categoryIds
|
|
delete params.image
|
|
const session = context.driver.session()
|
|
let updatePostCypher = `
|
|
MATCH (post:Post {id: $params.id})
|
|
SET post += $params
|
|
SET post.updatedAt = toString(datetime())
|
|
WITH post
|
|
`
|
|
|
|
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
|
const cypherDeletePreviousRelations = `
|
|
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
|
DELETE previousRelations
|
|
RETURN post, category
|
|
`
|
|
|
|
await session.writeTransaction((transaction) => {
|
|
return transaction.run(cypherDeletePreviousRelations, { params })
|
|
})
|
|
|
|
updatePostCypher += `
|
|
UNWIND $categoryIds AS categoryId
|
|
MATCH (category:Category {id: categoryId})
|
|
MERGE (post)-[:CATEGORIZED]->(category)
|
|
WITH post
|
|
`
|
|
}
|
|
|
|
if (params.postType) {
|
|
updatePostCypher += `
|
|
REMOVE post:Article
|
|
REMOVE post:Event
|
|
SET post:${params.postType}
|
|
WITH post
|
|
`
|
|
}
|
|
|
|
updatePostCypher += `RETURN post {.*, postType: [l IN labels(post) WHERE NOT l = 'Post']}`
|
|
const updatePostVariables = { categoryIds, params }
|
|
try {
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const updatePostTransactionResponse = await transaction.run(
|
|
updatePostCypher,
|
|
updatePostVariables,
|
|
)
|
|
const [post] = updatePostTransactionResponse.records.map((record) => record.get('post'))
|
|
await images(context.config).mergeImage(post, 'HERO_IMAGE', imageInput, { transaction })
|
|
return post
|
|
})
|
|
const post = await writeTxResultPromise
|
|
if (locationName) {
|
|
await createOrUpdateLocations('Post', post.id, locationName, session, context)
|
|
}
|
|
return post
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
},
|
|
|
|
DeletePost: async (_object, args, context: Context, _resolveInfo) => {
|
|
const session = context.driver.session()
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const deletePostTransactionResponse = await transaction.run(
|
|
`
|
|
MATCH (post:Post {id: $postId})
|
|
OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment)
|
|
SET post.deleted = TRUE
|
|
SET post.content = 'UNAVAILABLE'
|
|
SET post.contentExcerpt = 'UNAVAILABLE'
|
|
SET post.title = 'UNAVAILABLE'
|
|
SET comment.deleted = TRUE
|
|
RETURN post {.*}
|
|
`,
|
|
{ postId: args.id },
|
|
)
|
|
const [post] = deletePostTransactionResponse.records.map((record) => record.get('post'))
|
|
await images(context.config).deleteImage(post, 'HERO_IMAGE', { transaction })
|
|
return post
|
|
})
|
|
try {
|
|
const post = await writeTxResultPromise
|
|
return post
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
},
|
|
AddPostEmotions: async (_object, params, context: Context, _resolveInfo) => {
|
|
const { to, data } = params
|
|
const { user } = context
|
|
const session = context.driver.session()
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const addPostEmotionsTransactionResponse = await transaction.run(
|
|
`
|
|
MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
|
|
MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
|
|
RETURN userFrom, postTo, emotedRelation`,
|
|
{ user, to, data },
|
|
)
|
|
return addPostEmotionsTransactionResponse.records.map((record) => {
|
|
return {
|
|
from: { ...record.get('userFrom').properties },
|
|
to: { ...record.get('postTo').properties },
|
|
...record.get('emotedRelation').properties,
|
|
}
|
|
})
|
|
})
|
|
try {
|
|
const [emoted] = await writeTxResultPromise
|
|
return emoted
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
},
|
|
RemovePostEmotions: async (_object, params, context, _resolveInfo) => {
|
|
const { to, data } = params
|
|
const { id: from } = context.user
|
|
const session = context.driver.session()
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const removePostEmotionsTransactionResponse = await transaction.run(
|
|
`
|
|
MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
|
|
DELETE emotedRelation
|
|
RETURN userFrom, postTo
|
|
`,
|
|
{ from, to, data },
|
|
)
|
|
return removePostEmotionsTransactionResponse.records.map((record) => {
|
|
return {
|
|
from: { ...record.get('userFrom').properties },
|
|
to: { ...record.get('postTo').properties },
|
|
emotion: data.emotion,
|
|
}
|
|
})
|
|
})
|
|
try {
|
|
const [emoted] = await writeTxResultPromise
|
|
return emoted
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
pinPost: async (_parent, params, context: Context, _resolveInfo) => {
|
|
if (!context.user) {
|
|
throw new Error('Missing authenticated user.')
|
|
}
|
|
const { config } = context
|
|
if (config.MAX_PINNED_POSTS === 0) throw new Error('Pinned posts are not allowed!')
|
|
let pinnedPostWithNestedAttributes
|
|
const { driver, user } = context
|
|
const session = driver.session()
|
|
const { id: userId } = user
|
|
const pinPostCypher = `
|
|
MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
|
|
MATCH (post:Post {id: $params.id})
|
|
WHERE NOT EXISTS((post)-[:IN]->(:Group)) OR
|
|
(post)-[:IN]->(:Group { groupType: 'public'})
|
|
MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post)
|
|
SET post.pinned = true
|
|
RETURN post, pinned.createdAt as pinnedAt`
|
|
|
|
if (config.MAX_PINNED_POSTS === 1) {
|
|
let writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const deletePreviousRelationsResponse = await transaction.run(
|
|
`
|
|
MATCH (:User)-[previousRelations:PINNED]->(post:Post)
|
|
REMOVE post.pinned
|
|
DELETE previousRelations
|
|
RETURN post
|
|
`,
|
|
)
|
|
return deletePreviousRelationsResponse.records.map(
|
|
(record) => record.get('post').properties,
|
|
)
|
|
})
|
|
try {
|
|
await writeTxResultPromise
|
|
|
|
writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const pinPostTransactionResponse = await transaction.run(pinPostCypher, {
|
|
userId,
|
|
params,
|
|
})
|
|
return pinPostTransactionResponse.records.map((record) => ({
|
|
pinnedPost: record.get('post').properties,
|
|
pinnedAt: record.get('pinnedAt'),
|
|
}))
|
|
})
|
|
const [transactionResult] = await writeTxResultPromise
|
|
if (transactionResult) {
|
|
const { pinnedPost, pinnedAt } = transactionResult
|
|
pinnedPostWithNestedAttributes = {
|
|
...pinnedPost,
|
|
pinnedAt,
|
|
}
|
|
}
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
return pinnedPostWithNestedAttributes
|
|
} else {
|
|
const [currentPinnedPostCount] = (
|
|
await context.database.query({
|
|
query: `MATCH (:User)-[:PINNED]->(post:Post { pinned: true }) RETURN COUNT(post) AS count`,
|
|
})
|
|
).records.map((r) => Number(r.get('count').toString()))
|
|
if (currentPinnedPostCount >= config.MAX_PINNED_POSTS) {
|
|
throw new Error('Max number of pinned posts is reached!')
|
|
}
|
|
const [pinPostResult] = (
|
|
await context.database.write({
|
|
query: pinPostCypher,
|
|
variables: { userId, params },
|
|
})
|
|
).records.map((r) => ({
|
|
...r.get('post').properties,
|
|
pinnedAt: r.get('pinnedAt'),
|
|
}))
|
|
return pinPostResult
|
|
}
|
|
},
|
|
unpinPost: async (_parent, params, context, _resolveInfo) => {
|
|
let unpinnedPost
|
|
const session = context.driver.session()
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const unpinPostTransactionResponse = await transaction.run(
|
|
`
|
|
MATCH (:User)-[previousRelations:PINNED]->(post:Post {id: $params.id})
|
|
REMOVE post.pinned
|
|
DELETE previousRelations
|
|
RETURN post
|
|
`,
|
|
{ params },
|
|
)
|
|
return unpinPostTransactionResponse.records.map((record) => record.get('post').properties)
|
|
})
|
|
try {
|
|
;[unpinnedPost] = await writeTxResultPromise
|
|
} finally {
|
|
session.close()
|
|
}
|
|
return unpinnedPost
|
|
},
|
|
markTeaserAsViewed: async (_parent, params, context, _resolveInfo) => {
|
|
const session = context.driver.session()
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const transactionResponse = await transaction.run(
|
|
`
|
|
MATCH (post:Post { id: $params.id })
|
|
MATCH (user:User { id: $userId })
|
|
MERGE (user)-[relation:VIEWED_TEASER { }]->(post)
|
|
ON CREATE
|
|
SET relation.createdAt = toString(datetime()),
|
|
post.viewedTeaserCount = post.viewedTeaserCount + 1
|
|
RETURN post
|
|
`,
|
|
{ userId: context.user.id, params },
|
|
)
|
|
return transactionResponse.records.map((record) => record.get('post').properties)
|
|
})
|
|
try {
|
|
const [post] = await writeTxResultPromise
|
|
post.viewedTeaserCount = post.viewedTeaserCount.low
|
|
return post
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
toggleObservePost: async (_parent, params, context, _resolveInfo) => {
|
|
const session = context.driver.session()
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const transactionResponse = await transaction.run(
|
|
`
|
|
MATCH (post:Post { id: $params.id })
|
|
MATCH (user:User { id: $userId })
|
|
MERGE (user)-[obs:OBSERVES]->(post)
|
|
ON CREATE SET
|
|
obs.createdAt = toString(datetime()),
|
|
obs.updatedAt = toString(datetime()),
|
|
obs.active = $params.value
|
|
ON MATCH SET
|
|
obs.updatedAt = toString(datetime()),
|
|
obs.active = $params.value
|
|
RETURN post
|
|
`,
|
|
{ userId: context.user.id, params },
|
|
)
|
|
return transactionResponse.records.map((record) => record.get('post').properties)
|
|
})
|
|
try {
|
|
const [post] = await writeTxResultPromise
|
|
post.viewedTeaserCount = post.viewedTeaserCount.low
|
|
return post
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
pushPost: async (_parent, params, context: Context, _resolveInfo) => {
|
|
const posts = (
|
|
await context.database.write({
|
|
query: `
|
|
MATCH (post:Post {id: $id})
|
|
SET post.sortDate = toString(datetime())
|
|
RETURN post {.*}`,
|
|
variables: params,
|
|
})
|
|
).records.map((record) => record.get('post'))
|
|
|
|
if (posts.length !== 1) {
|
|
throw new Error('Could not find Post')
|
|
}
|
|
|
|
return posts[0]
|
|
},
|
|
unpushPost: async (_parent, params, context: Context, _resolveInfo) => {
|
|
const posts = (
|
|
await context.database.write({
|
|
query: `
|
|
MATCH (post:Post {id: $id})
|
|
SET post.sortDate = post.createdAt
|
|
RETURN post {.*}`,
|
|
variables: params,
|
|
})
|
|
).records.map((record) => record.get('post'))
|
|
|
|
if (posts.length !== 1) {
|
|
throw new Error('Could not find Post')
|
|
}
|
|
|
|
return posts[0]
|
|
},
|
|
},
|
|
Post: {
|
|
...Resolver('Post', {
|
|
undefinedToNull: [
|
|
'activityId',
|
|
'objectId',
|
|
'language',
|
|
'pinnedAt',
|
|
'pinned',
|
|
'eventVenue',
|
|
'eventLocation',
|
|
'eventLocationName',
|
|
'eventStart',
|
|
'eventEnd',
|
|
'eventIsOnline',
|
|
],
|
|
hasMany: {
|
|
tags: '-[:TAGGED]->(related:Tag)',
|
|
categories: '-[:CATEGORIZED]->(related:Category)',
|
|
comments: '<-[:COMMENTS]-(related:Comment)',
|
|
shoutedBy: '<-[:SHOUTED]-(related:User)',
|
|
emotions: '<-[related:EMOTED]',
|
|
},
|
|
hasOne: {
|
|
author: '<-[:WROTE]-(related:User)',
|
|
pinnedBy: '<-[:PINNED]-(related:User)',
|
|
image: '-[:HERO_IMAGE]->(related:Image)',
|
|
group: '-[:IN]->(related:Group)',
|
|
eventLocation: '-[:IS_IN]->(related:Location)',
|
|
},
|
|
count: {
|
|
commentsCount:
|
|
'<-[:COMMENTS]-(related:Comment) WHERE NOT related.deleted = true AND NOT related.disabled = true',
|
|
shoutedCount:
|
|
'<-[:SHOUTED]-(related:User) WHERE NOT related.deleted = true AND NOT related.disabled = true',
|
|
emotionsCount: '<-[related:EMOTED]-(:User)',
|
|
observingUsersCount:
|
|
'<-[obs:OBSERVES]-(related:User) WHERE obs.active = true AND NOT related.deleted = true AND NOT related.disabled = true',
|
|
},
|
|
boolean: {
|
|
shoutedByCurrentUser:
|
|
'MATCH(this)<-[:SHOUTED]-(related:User {id: $cypherParams.currentUserId}) RETURN COUNT(related) >= 1',
|
|
viewedTeaserByCurrentUser:
|
|
'MATCH (this)<-[:VIEWED_TEASER]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
|
isObservedByMe:
|
|
'MATCH (this)<-[obs:OBSERVES]-(related:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(related) >= 1',
|
|
},
|
|
}),
|
|
relatedContributions: async (parent, _params, context, _resolveInfo) => {
|
|
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
|
|
const { id } = parent
|
|
const session = context.driver.session()
|
|
|
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
const relatedContributionsTransactionResponse = await transaction.run(
|
|
`
|
|
MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
|
WHERE NOT post.deleted AND NOT post.disabled
|
|
RETURN DISTINCT post
|
|
LIMIT 10
|
|
`,
|
|
{ id },
|
|
)
|
|
return relatedContributionsTransactionResponse.records.map(
|
|
(record) => record.get('post').properties,
|
|
)
|
|
})
|
|
try {
|
|
const relatedContributions = await writeTxResultPromise
|
|
return relatedContributions
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
},
|
|
}
|