mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +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>
694 lines
24 KiB
TypeScript
694 lines
24 KiB
TypeScript
/* eslint-disable @typescript-eslint/require-await */
|
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
import { UserInputError, ForbiddenError } from 'apollo-server'
|
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
|
|
|
import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges'
|
|
import { getNeode } from '@db/neo4j'
|
|
import { Context } from '@src/context'
|
|
|
|
import { defaultTrophyBadge, defaultVerificationBadge } from './badges'
|
|
import normalizeEmail from './helpers/normalizeEmail'
|
|
import Resolver from './helpers/Resolver'
|
|
import { images } from './images/images'
|
|
import { createOrUpdateLocations } from './users/location'
|
|
|
|
const neode = getNeode()
|
|
|
|
export const getMutedUsers = async (context) => {
|
|
const { neode } = context
|
|
const userModel = neode.model('User')
|
|
let mutedUsers = neode
|
|
.query()
|
|
.match('user', userModel)
|
|
.where('user.id', context.user.id)
|
|
.relationship(userModel.relationships().get('muted'))
|
|
.to('muted', userModel)
|
|
.return('muted')
|
|
mutedUsers = await mutedUsers.execute()
|
|
mutedUsers = mutedUsers.records.map((r) => r.get('muted').properties)
|
|
return mutedUsers
|
|
}
|
|
|
|
export const getBlockedUsers = async (context) => {
|
|
const { neode } = context
|
|
const userModel = neode.model('User')
|
|
let blockedUsers = neode
|
|
.query()
|
|
.match('user', userModel)
|
|
.where('user.id', context.user.id)
|
|
.relationship(userModel.relationships().get('blocked'))
|
|
.to('blocked', userModel)
|
|
.return('blocked')
|
|
blockedUsers = await blockedUsers.execute()
|
|
blockedUsers = blockedUsers.records.map((r) => r.get('blocked').properties)
|
|
return blockedUsers
|
|
}
|
|
|
|
export default {
|
|
Query: {
|
|
mutedUsers: async (_object, _args, context, _resolveInfo) => {
|
|
try {
|
|
return getMutedUsers(context)
|
|
} catch (e) {
|
|
throw new UserInputError(e.message)
|
|
}
|
|
},
|
|
blockedUsers: async (_object, _args, context, _resolveInfo) => {
|
|
try {
|
|
return getBlockedUsers(context)
|
|
} catch (e) {
|
|
throw new UserInputError(e.message)
|
|
}
|
|
},
|
|
User: async (object, args, context, resolveInfo) => {
|
|
if (args.email) {
|
|
args.email = normalizeEmail(args.email)
|
|
let session
|
|
try {
|
|
session = context.driver.session()
|
|
const readTxResult = await session.readTransaction((txc) => {
|
|
const result = txc.run(
|
|
`
|
|
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()
|
|
}
|
|
}
|
|
return neo4jgraphql(object, args, context, resolveInfo)
|
|
},
|
|
},
|
|
Mutation: {
|
|
muteUser: async (_parent, params, context, _resolveInfo) => {
|
|
const { user: currentUser } = context
|
|
if (currentUser.id === params.id) return null
|
|
await neode.writeCypher(
|
|
`
|
|
MATCH(u:User {id: $currentUser.id})-[previousRelationship:FOLLOWS]->(b:User {id: $params.id})
|
|
DELETE previousRelationship
|
|
`,
|
|
{ currentUser, params },
|
|
)
|
|
const [user, mutedUser] = await Promise.all([
|
|
neode.find('User', currentUser.id),
|
|
neode.find('User', params.id),
|
|
])
|
|
await user.relateTo(mutedUser, 'muted')
|
|
return mutedUser.toJson()
|
|
},
|
|
unmuteUser: async (_parent, params, context, _resolveInfo) => {
|
|
const { user: currentUser } = context
|
|
if (currentUser.id === params.id) return null
|
|
await neode.writeCypher(
|
|
`
|
|
MATCH(u:User {id: $currentUser.id})-[previousRelationship:MUTED]->(b:User {id: $params.id})
|
|
DELETE previousRelationship
|
|
`,
|
|
{ currentUser, params },
|
|
)
|
|
const unmutedUser = await neode.find('User', params.id)
|
|
return unmutedUser.toJson()
|
|
},
|
|
blockUser: async (_object, args, context, _resolveInfo) => {
|
|
const { user: currentUser } = context
|
|
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 {
|
|
return await writeTxResultPromise
|
|
} catch (error) {
|
|
throw new UserInputError(error.message)
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
unblockUser: async (_object, args, context, _resolveInfo) => {
|
|
const { user: currentUser } = context
|
|
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 {
|
|
return await writeTxResultPromise
|
|
} catch (error) {
|
|
throw new UserInputError(error.message)
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
},
|
|
UpdateUser: async (_parent, params, context: Context, _resolveInfo) => {
|
|
const { avatar: avatarInput } = params
|
|
delete params.avatar
|
|
params.locationName = params.locationName === '' ? null : params.locationName
|
|
const { termsAndConditionsAgreedVersion } = params
|
|
if (termsAndConditionsAgreedVersion) {
|
|
const regEx = /^[0-9]+\.[0-9]+\.[0-9]+$/g
|
|
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
|
throw new ForbiddenError('Invalid version format!')
|
|
}
|
|
params.termsAndConditionsAgreedAt = new Date().toISOString()
|
|
}
|
|
|
|
const {
|
|
emailNotificationSettings,
|
|
}: { emailNotificationSettings: { name: string; value: boolean }[] | undefined } = params
|
|
delete params.emailNotificationSettings
|
|
if (emailNotificationSettings) {
|
|
emailNotificationSettings.forEach((setting) => {
|
|
params[
|
|
'emailNotifications' + setting.name.charAt(0).toUpperCase() + setting.name.slice(1)
|
|
] = setting.value
|
|
})
|
|
}
|
|
|
|
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
|
|
// 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 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
|
|
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
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
},
|
|
switchUserRole: async (_object, args, context, _resolveInfo) => {
|
|
const { role, id } = args
|
|
|
|
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
|
|
return user
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
saveCategorySettings: async (_object, args, context, _resolveInfo) => {
|
|
const { activeCategories } = args
|
|
const {
|
|
user: { id },
|
|
} = 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
|
|
return true
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
updateOnlineStatus: async (_object, args, context, _resolveInfo) => {
|
|
const { status } = args
|
|
const {
|
|
user: { id },
|
|
} = context
|
|
|
|
const CYPHER_AWAY = `
|
|
MATCH (user:User {id: $id})
|
|
WITH user,
|
|
CASE user.lastOnlineStatus
|
|
WHEN 'away' THEN user.awaySince
|
|
ELSE toString(datetime())
|
|
END AS awaySince
|
|
SET user.awaySince = awaySince
|
|
SET user.lastOnlineStatus = $status
|
|
`
|
|
const CYPHER_ONLINE = `
|
|
MATCH (user:User {id: $id})
|
|
SET user.awaySince = null
|
|
SET user.lastOnlineStatus = $status
|
|
`
|
|
|
|
// Last Online Time is saved as `lastActiveAt`
|
|
const session = context.driver.session()
|
|
await session.writeTransaction((transaction) => {
|
|
// return transaction.run(status === 'away' ? CYPHER_AWAY : CYPHER_ONLINE, { id, status })
|
|
return transaction.run(status === 'away' ? CYPHER_AWAY : CYPHER_ONLINE, { id, status })
|
|
})
|
|
|
|
return true
|
|
},
|
|
setTrophyBadgeSelected: async (_object, args, context, _resolveInfo) => {
|
|
const { slot, badgeId } = args
|
|
const {
|
|
user: { id: userId },
|
|
} = context
|
|
|
|
if (slot >= TROPHY_BADGES_SELECTED_MAX || slot < 0) {
|
|
throw new Error(
|
|
`Invalid slot! There is only ${TROPHY_BADGES_SELECTED_MAX} badge-slots to fill`,
|
|
)
|
|
}
|
|
|
|
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
|
|
if (!user) {
|
|
throw new Error('You cannot set badges not rewarded to you.')
|
|
}
|
|
return user
|
|
} catch (error) {
|
|
throw new Error(error)
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
resetTrophyBadgesSelected: async (_object, _args, context, _resolveInfo) => {
|
|
const {
|
|
user: { id: userId },
|
|
} = 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)
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
},
|
|
User: {
|
|
inviteCodes: async (_parent, _args, context: Context, _resolveInfo) => {
|
|
return (
|
|
await context.database.query({
|
|
query: `
|
|
MATCH (user:User {id: $user.id})-[:GENERATED]->(inviteCodes:InviteCode)
|
|
WHERE NOT (inviteCodes)-[:INVITES_TO]->(:Group)
|
|
RETURN inviteCodes {.*}
|
|
ORDER BY inviteCodes.createdAt ASC
|
|
`,
|
|
variables: { user: context.user },
|
|
})
|
|
).records.map((record) => record.get('inviteCodes'))
|
|
},
|
|
emailNotificationSettings: async (parent, _params, _context, _resolveInfo) => {
|
|
return [
|
|
{
|
|
type: 'post',
|
|
settings: [
|
|
{
|
|
name: 'commentOnObservedPost',
|
|
value: parent.emailNotificationsCommentOnObservedPost ?? true,
|
|
},
|
|
{
|
|
name: 'mention',
|
|
value: parent.emailNotificationsMention ?? true,
|
|
},
|
|
{
|
|
name: 'followingUsers',
|
|
value: parent.emailNotificationsFollowingUsers ?? true,
|
|
},
|
|
{
|
|
name: 'postInGroup',
|
|
value: parent.emailNotificationsPostInGroup ?? true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'chat',
|
|
settings: [
|
|
{
|
|
name: 'chatMessage',
|
|
value: parent.emailNotificationsChatMessage ?? true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'group',
|
|
settings: [
|
|
{
|
|
name: 'groupMemberJoined',
|
|
value: parent.emailNotificationsGroupMemberJoined ?? true,
|
|
},
|
|
{
|
|
name: 'groupMemberLeft',
|
|
value: parent.emailNotificationsGroupMemberLeft ?? true,
|
|
},
|
|
{
|
|
name: 'groupMemberRemoved',
|
|
value: parent.emailNotificationsGroupMemberRemoved ?? true,
|
|
},
|
|
{
|
|
name: 'groupMemberRoleChanged',
|
|
value: parent.emailNotificationsGroupMemberRoleChanged ?? true,
|
|
},
|
|
],
|
|
},
|
|
]
|
|
},
|
|
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 result = Array(TROPHY_BADGES_SELECTED_MAX).fill(defaultTrophyBadge)
|
|
badgesSelected.map((record) => {
|
|
result[record.get('slot')] = record.get('badge')
|
|
return true
|
|
})
|
|
return result
|
|
} catch (error) {
|
|
throw new Error(error)
|
|
} finally {
|
|
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)
|
|
} finally {
|
|
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)
|
|
} finally {
|
|
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
|
|
return result ?? defaultVerificationBadge
|
|
} catch (error) {
|
|
throw new Error(error)
|
|
} finally {
|
|
session.close()
|
|
}
|
|
},
|
|
...Resolver('User', {
|
|
undefinedToNull: [
|
|
'actorId',
|
|
'deleted',
|
|
'disabled',
|
|
'locationName',
|
|
'about',
|
|
'termsAndConditionsAgreedVersion',
|
|
'termsAndConditionsAgreedAt',
|
|
'allowEmbedIframes',
|
|
'showShoutsPublicly',
|
|
'locale',
|
|
],
|
|
boolean: {
|
|
followedByCurrentUser:
|
|
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
|
isBlocked:
|
|
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
|
blocked:
|
|
'MATCH (this)-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
|
isMuted:
|
|
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
|
},
|
|
count: {
|
|
contributionsCount:
|
|
'-[:WROTE]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true',
|
|
friendsCount: '<-[:FRIENDS]->(related:User)',
|
|
followingCount: '-[:FOLLOWS]->(related:User)',
|
|
followedByCount: '<-[:FOLLOWS]-(related:User)',
|
|
commentedCount:
|
|
'-[:WROTE]->(c:Comment)-[:COMMENTS]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true',
|
|
shoutedCount:
|
|
'-[:SHOUTED]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true',
|
|
badgeTrophiesCount: '<-[:REWARDED]-(related:Badge)',
|
|
},
|
|
hasOne: {
|
|
avatar: '-[:AVATAR_IMAGE]->(related:Image)',
|
|
invitedBy: '<-[:INVITED]-(related:User)',
|
|
location: '-[:IS_IN]->(related:Location)',
|
|
redeemedInviteCode: '-[:REDEEMED]->(related:InviteCode)',
|
|
},
|
|
hasMany: {
|
|
followedBy: '<-[:FOLLOWS]-(related:User)',
|
|
following: '-[:FOLLOWS]->(related:User)',
|
|
friends: '-[:FRIENDS]-(related:User)',
|
|
socialMedia: '<-[:OWNED_BY]-(related:SocialMedia)',
|
|
contributions: '-[:WROTE]->(related:Post)',
|
|
comments: '-[:WROTE]->(related:Comment)',
|
|
shouted: '-[:SHOUTED]->(related:Post)',
|
|
categories: '-[:CATEGORIZED]->(related:Category)',
|
|
badgeTrophies: '<-[:REWARDED]-(related:Badge)',
|
|
},
|
|
}),
|
|
},
|
|
}
|