mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #2433 from Human-Connection/2412-favor-transaction-function
Favor transaction functions
This commit is contained in:
commit
56c5f4a384
@ -11,27 +11,28 @@ export default async (driver, authorizationHeader) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const query = `
|
const session = driver.session()
|
||||||
|
|
||||||
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const updateUserLastActiveTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (user:User {id: $id, deleted: false, disabled: false })
|
MATCH (user:User {id: $id, deleted: false, disabled: false })
|
||||||
SET user.lastActiveAt = toString(datetime())
|
SET user.lastActiveAt = toString(datetime())
|
||||||
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
|
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`
|
`,
|
||||||
const session = driver.session()
|
{ id },
|
||||||
let result
|
)
|
||||||
|
return updateUserLastActiveTransactionResponse.records.map(record => record.get('user'))
|
||||||
try {
|
|
||||||
result = await session.run(query, { id })
|
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
const [currentUser] = await result.records.map(record => {
|
|
||||||
return record.get('user')
|
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [currentUser] = await writeTxResultPromise
|
||||||
if (!currentUser) return null
|
if (!currentUser) return null
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
...currentUser,
|
...currentUser,
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,30 +2,23 @@ import extractHashtags from '../hashtags/extractHashtags'
|
|||||||
|
|
||||||
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
||||||
if (!hashtags.length) return
|
if (!hashtags.length) return
|
||||||
|
|
||||||
// We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement
|
|
||||||
// functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted
|
|
||||||
// and no new Hashtags and relations will be created.
|
|
||||||
const cypherDeletePreviousRelations = `
|
|
||||||
MATCH (p: Post { id: $postId })-[previousRelations: TAGGED]->(t: Tag)
|
|
||||||
DELETE previousRelations
|
|
||||||
RETURN p, t
|
|
||||||
`
|
|
||||||
const cypherCreateNewTagsAndRelations = `
|
|
||||||
MATCH (p: Post { id: $postId})
|
|
||||||
UNWIND $hashtags AS tagName
|
|
||||||
MERGE (t: Tag { id: tagName, disabled: false, deleted: false })
|
|
||||||
MERGE (p)-[:TAGGED]->(t)
|
|
||||||
RETURN p, t
|
|
||||||
`
|
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await session.run(cypherDeletePreviousRelations, {
|
await session.writeTransaction(txc => {
|
||||||
postId,
|
return txc.run(
|
||||||
})
|
`
|
||||||
await session.run(cypherCreateNewTagsAndRelations, {
|
MATCH (post:Post { id: $postId})
|
||||||
postId,
|
OPTIONAL MATCH (post)-[previousRelations:TAGGED]->(tag:Tag)
|
||||||
hashtags,
|
DELETE previousRelations
|
||||||
|
WITH post
|
||||||
|
UNWIND $hashtags AS tagName
|
||||||
|
MERGE (tag:Tag {id: tagName, disabled: false, deleted: false })
|
||||||
|
MERGE (post)-[:TAGGED]->(tag)
|
||||||
|
RETURN post, tag
|
||||||
|
`,
|
||||||
|
{ postId, hashtags },
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import sluggify from './sluggifyMiddleware'
|
|||||||
import excerpt from './excerptMiddleware'
|
import excerpt from './excerptMiddleware'
|
||||||
import xss from './xssMiddleware'
|
import xss from './xssMiddleware'
|
||||||
import permissions from './permissionsMiddleware'
|
import permissions from './permissionsMiddleware'
|
||||||
import user from './userMiddleware'
|
import user from './user/userMiddleware'
|
||||||
import includedFields from './includedFieldsMiddleware'
|
import includedFields from './includedFieldsMiddleware'
|
||||||
import orderBy from './orderByMiddleware'
|
import orderBy from './orderByMiddleware'
|
||||||
import validation from './validation/validationMiddleware'
|
import validation from './validation/validationMiddleware'
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const createLocation = async (session, mapboxData) => {
|
|||||||
lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null,
|
lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null,
|
||||||
}
|
}
|
||||||
|
|
||||||
let query =
|
let mutation =
|
||||||
'MERGE (l:Location {id: $id}) ' +
|
'MERGE (l:Location {id: $id}) ' +
|
||||||
'SET l.name = $nameEN, ' +
|
'SET l.name = $nameEN, ' +
|
||||||
'l.nameEN = $nameEN, ' +
|
'l.nameEN = $nameEN, ' +
|
||||||
@ -53,19 +53,23 @@ const createLocation = async (session, mapboxData) => {
|
|||||||
'l.type = $type'
|
'l.type = $type'
|
||||||
|
|
||||||
if (data.lat && data.lng) {
|
if (data.lat && data.lng) {
|
||||||
query += ', l.lat = $lat, l.lng = $lng'
|
mutation += ', l.lat = $lat, l.lng = $lng'
|
||||||
}
|
}
|
||||||
query += ' RETURN l.id'
|
mutation += ' RETURN l.id'
|
||||||
|
|
||||||
await session.run(query, data)
|
try {
|
||||||
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(mutation, data)
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createOrUpdateLocations = async (userId, locationName, driver) => {
|
const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||||
if (isEmpty(locationName)) {
|
if (isEmpty(locationName)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
||||||
locationName,
|
locationName,
|
||||||
@ -106,33 +110,44 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
|
|||||||
if (data.context) {
|
if (data.context) {
|
||||||
await asyncForEach(data.context, async ctx => {
|
await asyncForEach(data.context, async ctx => {
|
||||||
await createLocation(session, ctx)
|
await createLocation(session, ctx)
|
||||||
|
try {
|
||||||
await session.run(
|
await session.writeTransaction(transaction => {
|
||||||
'MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) ' +
|
return transaction.run(
|
||||||
'MERGE (child)<-[:IS_IN]-(parent) ' +
|
`
|
||||||
'RETURN child.id, parent.id',
|
MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
|
||||||
|
MERGE (child)<-[:IS_IN]-(parent)
|
||||||
|
RETURN child.id, parent.id
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
parentId: parent.id,
|
parentId: parent.id,
|
||||||
childId: ctx.id,
|
childId: ctx.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
})
|
||||||
parent = ctx
|
parent = ctx
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// delete all current locations from user
|
// delete all current locations from user and add new location
|
||||||
await session.run('MATCH (u:User {id: $userId})-[r:IS_IN]->(l:Location) DETACH DELETE r', {
|
try {
|
||||||
userId: userId,
|
await session.writeTransaction(transaction => {
|
||||||
})
|
return transaction.run(
|
||||||
// connect user with location
|
`
|
||||||
await session.run(
|
MATCH (user:User {id: $userId})-[relationship:IS_IN]->(location:Location)
|
||||||
'MATCH (u:User {id: $userId}), (l:Location {id: $locationId}) MERGE (u)-[:IS_IN]->(l) RETURN l.id, u.id',
|
DETACH DELETE relationship
|
||||||
{
|
WITH user
|
||||||
userId: userId,
|
MATCH (location:Location {id: $locationId})
|
||||||
locationId: data.id,
|
MERGE (user)-[:IS_IN]->(location)
|
||||||
},
|
RETURN location.id, user.id
|
||||||
|
`,
|
||||||
|
{ userId: userId, locationId: data.id },
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createOrUpdateLocations
|
export default createOrUpdateLocations
|
||||||
|
|||||||
@ -1,66 +1,73 @@
|
|||||||
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
||||||
|
import { validateNotifyUsers } from '../validation/validationMiddleware'
|
||||||
|
|
||||||
const postAuthorOfComment = async (comment, { context }) => {
|
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
||||||
const cypherFindUser = `
|
const idsOfUsers = extractMentionedUsers(args.content)
|
||||||
MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
|
const post = await resolve(root, args, context, resolveInfo)
|
||||||
RETURN user { .id }
|
if (post && idsOfUsers && idsOfUsers.length)
|
||||||
`
|
await notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
|
||||||
|
const { content } = args
|
||||||
|
let idsOfUsers = extractMentionedUsers(content)
|
||||||
|
const comment = await resolve(root, args, context, resolveInfo)
|
||||||
|
const [postAuthor] = await postAuthorOfComment(comment.id, { context })
|
||||||
|
idsOfUsers = idsOfUsers.filter(id => id !== postAuthor.id)
|
||||||
|
if (idsOfUsers && idsOfUsers.length)
|
||||||
|
await notifyUsersOfMention('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context)
|
||||||
|
if (context.user.id !== postAuthor.id)
|
||||||
|
await notifyUsersOfComment('Comment', comment.id, postAuthor.id, 'commented_on_post', context)
|
||||||
|
return comment
|
||||||
|
}
|
||||||
|
|
||||||
|
const postAuthorOfComment = async (commentId, { context }) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
let result
|
let postAuthorId
|
||||||
try {
|
try {
|
||||||
result = await session.run(cypherFindUser, {
|
postAuthorId = await session.readTransaction(transaction => {
|
||||||
commentId: comment.id,
|
return transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (author:User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
|
||||||
|
RETURN author { .id } as authorId
|
||||||
|
`,
|
||||||
|
{ commentId },
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
return postAuthorId.records.map(record => record.get('authorId'))
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
const [postAuthor] = await result.records.map(record => {
|
|
||||||
return record.get('user')
|
|
||||||
})
|
|
||||||
return postAuthor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
|
const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||||
if (!idsOfUsers.length) return
|
await validateNotifyUsers(label, reason)
|
||||||
|
let mentionedCypher
|
||||||
// Checked here, because it does not go through GraphQL checks at all in this file.
|
|
||||||
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
|
|
||||||
if (!reasonsAllowed.includes(reason)) {
|
|
||||||
throw new Error('Notification reason is not allowed!')
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(label === 'Post' && reason !== 'mentioned_in_post') ||
|
|
||||||
(label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason))
|
|
||||||
) {
|
|
||||||
throw new Error('Notification does not fit the reason!')
|
|
||||||
}
|
|
||||||
|
|
||||||
let cypher
|
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case 'mentioned_in_post': {
|
case 'mentioned_in_post': {
|
||||||
cypher = `
|
mentionedCypher = `
|
||||||
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
||||||
MATCH (user: User)
|
MATCH (user: User)
|
||||||
WHERE user.id in $idsOfUsers
|
WHERE user.id in $idsOfUsers
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
AND NOT (user)<-[:BLOCKED]-(author)
|
||||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||||
SET notification.read = FALSE
|
|
||||||
SET (
|
|
||||||
CASE
|
|
||||||
WHEN notification.createdAt IS NULL
|
|
||||||
THEN notification END ).createdAt = toString(datetime())
|
|
||||||
SET notification.updatedAt = toString(datetime())
|
|
||||||
`
|
`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'mentioned_in_comment': {
|
case 'mentioned_in_comment': {
|
||||||
cypher = `
|
mentionedCypher = `
|
||||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
||||||
MATCH (user: User)
|
MATCH (user: User)
|
||||||
WHERE user.id in $idsOfUsers
|
WHERE user.id in $idsOfUsers
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
AND NOT (user)<-[:BLOCKED]-(author)
|
||||||
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
||||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||||
|
`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mentionedCypher += `
|
||||||
SET notification.read = FALSE
|
SET notification.read = FALSE
|
||||||
SET (
|
SET (
|
||||||
CASE
|
CASE
|
||||||
@ -68,97 +75,47 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
|
|||||||
THEN notification END ).createdAt = toString(datetime())
|
THEN notification END ).createdAt = toString(datetime())
|
||||||
SET notification.updatedAt = toString(datetime())
|
SET notification.updatedAt = toString(datetime())
|
||||||
`
|
`
|
||||||
break
|
const session = context.driver.session()
|
||||||
|
try {
|
||||||
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(mentionedCypher, { id, idsOfUsers, reason })
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
}
|
}
|
||||||
case 'commented_on_post': {
|
}
|
||||||
cypher = `
|
|
||||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
|
||||||
MATCH (user: User)
|
await validateNotifyUsers(label, reason)
|
||||||
WHERE user.id in $idsOfUsers
|
const session = context.driver.session()
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
|
||||||
AND NOT (author)<-[:BLOCKED]-(user)
|
try {
|
||||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
await session.writeTransaction(async transaction => {
|
||||||
|
await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||||
|
WHERE NOT (postAuthor)-[:BLOCKED]-(commenter)
|
||||||
|
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor)
|
||||||
SET notification.read = FALSE
|
SET notification.read = FALSE
|
||||||
SET (
|
SET (
|
||||||
CASE
|
CASE
|
||||||
WHEN notification.createdAt IS NULL
|
WHEN notification.createdAt IS NULL
|
||||||
THEN notification END ).createdAt = toString(datetime())
|
THEN notification END ).createdAt = toString(datetime())
|
||||||
SET notification.updatedAt = toString(datetime())
|
SET notification.updatedAt = toString(datetime())
|
||||||
`
|
`,
|
||||||
break
|
{ commentId, postAuthorId, reason },
|
||||||
}
|
)
|
||||||
}
|
|
||||||
const session = context.driver.session()
|
|
||||||
try {
|
|
||||||
await session.run(cypher, {
|
|
||||||
id,
|
|
||||||
idsOfUsers,
|
|
||||||
reason,
|
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
|
||||||
const idsOfUsers = extractMentionedUsers(args.content)
|
|
||||||
|
|
||||||
const post = await resolve(root, args, context, resolveInfo)
|
|
||||||
|
|
||||||
if (post) {
|
|
||||||
await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context)
|
|
||||||
}
|
|
||||||
|
|
||||||
return post
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
|
|
||||||
let idsOfUsers = extractMentionedUsers(args.content)
|
|
||||||
const comment = await resolve(root, args, context, resolveInfo)
|
|
||||||
|
|
||||||
if (comment) {
|
|
||||||
const postAuthor = await postAuthorOfComment(comment, { context })
|
|
||||||
idsOfUsers = idsOfUsers.filter(id => id !== postAuthor.id)
|
|
||||||
|
|
||||||
await notifyUsers('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context)
|
|
||||||
}
|
|
||||||
|
|
||||||
return comment
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCreateComment = async (resolve, root, args, context, resolveInfo) => {
|
|
||||||
const comment = await handleContentDataOfComment(resolve, root, args, context, resolveInfo)
|
|
||||||
|
|
||||||
if (comment) {
|
|
||||||
const cypherFindUser = `
|
|
||||||
MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
|
|
||||||
RETURN user { .id }
|
|
||||||
`
|
|
||||||
const session = context.driver.session()
|
|
||||||
let result
|
|
||||||
try {
|
|
||||||
result = await session.run(cypherFindUser, {
|
|
||||||
commentId: comment.id,
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
const [postAuthor] = await result.records.map(record => {
|
|
||||||
return record.get('user')
|
|
||||||
})
|
|
||||||
if (context.user.id !== postAuthor.id) {
|
|
||||||
await notifyUsers('Comment', comment.id, [postAuthor.id], 'commented_on_post', context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return comment
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreatePost: handleContentDataOfPost,
|
CreatePost: handleContentDataOfPost,
|
||||||
UpdatePost: handleContentDataOfPost,
|
UpdatePost: handleContentDataOfPost,
|
||||||
CreateComment: handleCreateComment,
|
CreateComment: handleContentDataOfComment,
|
||||||
UpdateComment: handleContentDataOfComment,
|
UpdateComment: handleContentDataOfComment,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,7 @@ import { createTestClient } from 'apollo-server-testing'
|
|||||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
let server
|
let server, query, mutate, notifiedUser, authenticatedUser
|
||||||
let query
|
|
||||||
let mutate
|
|
||||||
let notifiedUser
|
|
||||||
let authenticatedUser
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
@ -39,7 +35,8 @@ const createCommentMutation = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(async () => {
|
||||||
|
await factory.cleanDatabase()
|
||||||
const createServerResult = createServer({
|
const createServerResult = createServer({
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
@ -173,7 +170,6 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -190,7 +186,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -214,7 +210,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -265,7 +261,7 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -409,7 +405,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -467,7 +463,7 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -501,7 +497,7 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -532,7 +528,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
|
|||||||
@ -47,17 +47,18 @@ const isAuthor = rule({
|
|||||||
if (!user) return false
|
if (!user) return false
|
||||||
const { id: resourceId } = args
|
const { id: resourceId } = args
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
const authorReadTxPromise = session.readTransaction(async transaction => {
|
||||||
const result = await session.run(
|
const authorTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (resource {id: $resourceId})<-[:WROTE]-(author {id: $userId})
|
MATCH (resource {id: $resourceId})<-[:WROTE]-(author {id: $userId})
|
||||||
RETURN author
|
RETURN author
|
||||||
`,
|
`,
|
||||||
{ resourceId, userId: user.id },
|
{ resourceId, userId: user.id },
|
||||||
)
|
)
|
||||||
const [author] = result.records.map(record => {
|
return authorTransactionResponse.records.map(record => record.get('author'))
|
||||||
return record.get('author')
|
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [author] = await authorReadTxPromise
|
||||||
return !!author
|
return !!author
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -4,10 +4,16 @@ const isUniqueFor = (context, type) => {
|
|||||||
return async slug => {
|
return async slug => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const response = await session.run(`MATCH(p:${type} {slug: $slug }) return p.slug`, {
|
const existingSlug = await session.readTransaction(transaction => {
|
||||||
slug,
|
return transaction.run(
|
||||||
|
`
|
||||||
|
MATCH(p:${type} {slug: $slug })
|
||||||
|
RETURN p.slug
|
||||||
|
`,
|
||||||
|
{ slug },
|
||||||
|
)
|
||||||
})
|
})
|
||||||
return response.records.length === 0
|
return existingSlug.records.length === 0
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import createOrUpdateLocations from './nodes/locations'
|
import createOrUpdateLocations from '../nodes/locations'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
SignupVerification: async (resolve, root, args, context, info) => {
|
SignupVerification: async (resolve, root, args, context, info) => {
|
||||||
const result = await resolve(root, args, context, info)
|
const result = await resolve(root, args, context, info)
|
||||||
await createOrUpdateLocations(args.id, args.locationName, context.driver)
|
await createOrUpdateLocations(result.id, args.locationName, context.driver)
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
UpdateUser: async (resolve, root, args, context, info) => {
|
UpdateUser: async (resolve, root, args, context, info) => {
|
||||||
213
backend/src/middleware/user/userMiddleware.spec.js
Normal file
213
backend/src/middleware/user/userMiddleware.spec.js
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { gql } from '../../helpers/jest'
|
||||||
|
import Factory from '../../seed/factories'
|
||||||
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
import createServer from '../../server'
|
||||||
|
|
||||||
|
const factory = Factory()
|
||||||
|
const neode = getNeode()
|
||||||
|
const driver = getDriver()
|
||||||
|
let authenticatedUser, mutate, variables
|
||||||
|
|
||||||
|
const signupVerificationMutation = gql`
|
||||||
|
mutation(
|
||||||
|
$name: String!
|
||||||
|
$password: String!
|
||||||
|
$email: String!
|
||||||
|
$nonce: String!
|
||||||
|
$termsAndConditionsAgreedVersion: String!
|
||||||
|
$locationName: String
|
||||||
|
) {
|
||||||
|
SignupVerification(
|
||||||
|
name: $name
|
||||||
|
password: $password
|
||||||
|
email: $email
|
||||||
|
nonce: $nonce
|
||||||
|
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
||||||
|
locationName: $locationName
|
||||||
|
) {
|
||||||
|
locationName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const updateUserMutation = gql`
|
||||||
|
mutation($id: ID!, $name: String!, $locationName: String) {
|
||||||
|
UpdateUser(id: $id, name: $name, locationName: $locationName) {
|
||||||
|
locationName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
let newlyCreatedNodesWithLocales = [
|
||||||
|
{
|
||||||
|
city: {
|
||||||
|
lng: 41.1534,
|
||||||
|
nameES: 'Hamburg',
|
||||||
|
nameFR: 'Hamburg',
|
||||||
|
nameIT: 'Hamburg',
|
||||||
|
nameEN: 'Hamburg',
|
||||||
|
type: 'place',
|
||||||
|
namePT: 'Hamburg',
|
||||||
|
nameRU: 'Хамбург',
|
||||||
|
nameDE: 'Hamburg',
|
||||||
|
nameNL: 'Hamburg',
|
||||||
|
name: 'Hamburg',
|
||||||
|
namePL: 'Hamburg',
|
||||||
|
id: 'place.5977106083398860',
|
||||||
|
lat: -74.5763,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
namePT: 'Nova Jérsia',
|
||||||
|
nameRU: 'Нью-Джерси',
|
||||||
|
nameDE: 'New Jersey',
|
||||||
|
nameNL: 'New Jersey',
|
||||||
|
nameES: 'Nueva Jersey',
|
||||||
|
name: 'New Jersey',
|
||||||
|
namePL: 'New Jersey',
|
||||||
|
nameFR: 'New Jersey',
|
||||||
|
nameIT: 'New Jersey',
|
||||||
|
id: 'region.14919479731700330',
|
||||||
|
nameEN: 'New Jersey',
|
||||||
|
type: 'region',
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
namePT: 'Estados Unidos',
|
||||||
|
nameRU: 'Соединённые Штаты Америки',
|
||||||
|
nameDE: 'Vereinigte Staaten',
|
||||||
|
nameNL: 'Verenigde Staten van Amerika',
|
||||||
|
nameES: 'Estados Unidos',
|
||||||
|
namePL: 'Stany Zjednoczone',
|
||||||
|
name: 'United States of America',
|
||||||
|
nameFR: 'États-Unis',
|
||||||
|
nameIT: "Stati Uniti d'America",
|
||||||
|
id: 'country.9053006287256050',
|
||||||
|
nameEN: 'United States of America',
|
||||||
|
type: 'country',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
const { server } = createServer({
|
||||||
|
context: () => {
|
||||||
|
return {
|
||||||
|
user: authenticatedUser,
|
||||||
|
neode,
|
||||||
|
driver,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mutate = createTestClient(server).mutate
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
variables = {}
|
||||||
|
authenticatedUser = null
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
factory.cleanDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('userMiddleware', () => {
|
||||||
|
describe('SignupVerification', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
variables = {
|
||||||
|
...variables,
|
||||||
|
name: 'John Doe',
|
||||||
|
password: '123',
|
||||||
|
email: 'john@example.org',
|
||||||
|
nonce: '123456',
|
||||||
|
termsAndConditionsAgreedVersion: '0.1.0',
|
||||||
|
locationName: 'Hamburg, New Jersey, United States of America',
|
||||||
|
}
|
||||||
|
const args = {
|
||||||
|
email: 'john@example.org',
|
||||||
|
nonce: '123456',
|
||||||
|
}
|
||||||
|
await neode.model('EmailAddress').create(args)
|
||||||
|
})
|
||||||
|
it('creates a Location node with localised city/state/country names', async () => {
|
||||||
|
await mutate({ mutation: signupVerificationMutation, variables })
|
||||||
|
const locations = await neode.cypher(
|
||||||
|
`MATCH (city:Location)-[:IS_IN]->(state:Location)-[:IS_IN]->(country:Location) return city, state, country`,
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
locations.records.map(record => {
|
||||||
|
return {
|
||||||
|
city: record.get('city').properties,
|
||||||
|
state: record.get('state').properties,
|
||||||
|
country: record.get('country').properties,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
).toEqual(newlyCreatedNodesWithLocales)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UpdateUser', () => {
|
||||||
|
let user, userParams
|
||||||
|
beforeEach(async () => {
|
||||||
|
newlyCreatedNodesWithLocales = [
|
||||||
|
{
|
||||||
|
city: {
|
||||||
|
lng: 53.55,
|
||||||
|
nameES: 'Hamburgo',
|
||||||
|
nameFR: 'Hambourg',
|
||||||
|
nameIT: 'Amburgo',
|
||||||
|
nameEN: 'Hamburg',
|
||||||
|
type: 'region',
|
||||||
|
namePT: 'Hamburgo',
|
||||||
|
nameRU: 'Гамбург',
|
||||||
|
nameDE: 'Hamburg',
|
||||||
|
nameNL: 'Hamburg',
|
||||||
|
namePL: 'Hamburg',
|
||||||
|
name: 'Hamburg',
|
||||||
|
id: 'region.10793468240398860',
|
||||||
|
lat: 10,
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
namePT: 'Alemanha',
|
||||||
|
nameRU: 'Германия',
|
||||||
|
nameDE: 'Deutschland',
|
||||||
|
nameNL: 'Duitsland',
|
||||||
|
nameES: 'Alemania',
|
||||||
|
name: 'Germany',
|
||||||
|
namePL: 'Niemcy',
|
||||||
|
nameFR: 'Allemagne',
|
||||||
|
nameIT: 'Germania',
|
||||||
|
id: 'country.10743216036480410',
|
||||||
|
nameEN: 'Germany',
|
||||||
|
type: 'country',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
userParams = {
|
||||||
|
id: 'updating-user',
|
||||||
|
}
|
||||||
|
user = await factory.create('User', userParams)
|
||||||
|
authenticatedUser = await user.toJson()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a Location node with localised city/state/country names', async () => {
|
||||||
|
variables = {
|
||||||
|
...variables,
|
||||||
|
id: 'updating-user',
|
||||||
|
name: 'Updating user',
|
||||||
|
locationName: 'Hamburg, Germany',
|
||||||
|
}
|
||||||
|
await mutate({ mutation: updateUserMutation, variables })
|
||||||
|
const locations = await neode.cypher(
|
||||||
|
`MATCH (city:Location)-[:IS_IN]->(country:Location) return city, country`,
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
locations.records.map(record => {
|
||||||
|
return {
|
||||||
|
city: record.get('city').properties,
|
||||||
|
country: record.get('country').properties,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
).toEqual(newlyCreatedNodesWithLocales)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -4,7 +4,7 @@ const COMMENT_MIN_LENGTH = 1
|
|||||||
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
|
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
|
||||||
const NO_CATEGORIES_ERR_MESSAGE =
|
const NO_CATEGORIES_ERR_MESSAGE =
|
||||||
'You cannot save a post without at least one category or more than three'
|
'You cannot save a post without at least one category or more than three'
|
||||||
|
const USERNAME_MIN_LENGTH = 3
|
||||||
const validateCreateComment = async (resolve, root, args, context, info) => {
|
const validateCreateComment = async (resolve, root, args, context, info) => {
|
||||||
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
||||||
const { postId } = args
|
const { postId } = args
|
||||||
@ -14,14 +14,15 @@ const validateCreateComment = async (resolve, root, args, context, info) => {
|
|||||||
}
|
}
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const postQueryRes = await session.run(
|
const postQueryRes = await session.readTransaction(transaction => {
|
||||||
|
return transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (post:Post {id: $postId})
|
MATCH (post:Post {id: $postId})
|
||||||
RETURN post`,
|
RETURN post
|
||||||
{
|
`,
|
||||||
postId,
|
{ postId },
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
})
|
||||||
const [post] = postQueryRes.records.map(record => {
|
const [post] = postQueryRes.records.map(record => {
|
||||||
return record.get('post')
|
return record.get('post')
|
||||||
})
|
})
|
||||||
@ -72,8 +73,8 @@ const validateReview = async (resolve, root, args, context, info) => {
|
|||||||
const { user, driver } = context
|
const { user, driver } = context
|
||||||
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const reportReadTxPromise = session.writeTransaction(async txc => {
|
const reportReadTxPromise = session.readTransaction(async transaction => {
|
||||||
const validateReviewTransactionResponse = await txc.run(
|
const validateReviewTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (resource {id: $resourceId})
|
MATCH (resource {id: $resourceId})
|
||||||
WHERE resource:User OR resource:Post OR resource:Comment
|
WHERE resource:User OR resource:Post OR resource:Comment
|
||||||
@ -115,12 +116,31 @@ const validateReview = async (resolve, root, args, context, info) => {
|
|||||||
return resolve(root, args, context, info)
|
return resolve(root, args, context, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const validateNotifyUsers = async (label, reason) => {
|
||||||
|
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
|
||||||
|
if (!reasonsAllowed.includes(reason)) throw new Error('Notification reason is not allowed!')
|
||||||
|
if (
|
||||||
|
(label === 'Post' && reason !== 'mentioned_in_post') ||
|
||||||
|
(label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason))
|
||||||
|
) {
|
||||||
|
throw new Error('Notification does not fit the reason!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateUpdateUser = async (resolve, root, params, context, info) => {
|
||||||
|
const { name } = params
|
||||||
|
if (typeof name === 'string' && name.trim().length < USERNAME_MIN_LENGTH)
|
||||||
|
throw new UserInputError(`Username must be at least ${USERNAME_MIN_LENGTH} character long!`)
|
||||||
|
return resolve(root, params, context, info)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreateComment: validateCreateComment,
|
CreateComment: validateCreateComment,
|
||||||
UpdateComment: validateUpdateComment,
|
UpdateComment: validateUpdateComment,
|
||||||
CreatePost: validatePost,
|
CreatePost: validatePost,
|
||||||
UpdatePost: validateUpdatePost,
|
UpdatePost: validateUpdatePost,
|
||||||
|
UpdateUser: validateUpdateUser,
|
||||||
fileReport: validateReport,
|
fileReport: validateReport,
|
||||||
review: validateReview,
|
review: validateReview,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -71,6 +71,14 @@ const reviewMutation = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const updateUserMutation = gql`
|
||||||
|
mutation($id: ID!, $name: String) {
|
||||||
|
UpdateUser(id: $id, name: $name) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const { server } = createServer({
|
const { server } = createServer({
|
||||||
context: () => {
|
context: () => {
|
||||||
@ -397,4 +405,33 @@ describe('validateReview', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('validateUpdateUser', () => {
|
||||||
|
let userParams, variables, updatingUser
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
userParams = {
|
||||||
|
id: 'updating-user',
|
||||||
|
name: 'John Doe',
|
||||||
|
}
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
id: 'updating-user',
|
||||||
|
name: 'John Doughnut',
|
||||||
|
}
|
||||||
|
updatingUser = await factory.create('User', userParams)
|
||||||
|
authenticatedUser = await updatingUser.toJson()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('with name too short', async () => {
|
||||||
|
variables = {
|
||||||
|
...variables,
|
||||||
|
name: ' ',
|
||||||
|
}
|
||||||
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
|
||||||
|
data: { UpdateUser: null },
|
||||||
|
errors: [{ message: 'Username must be at least 3 character long!' }],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export default {
|
|||||||
Mutation: {
|
Mutation: {
|
||||||
CreateComment: async (object, params, context, resolveInfo) => {
|
CreateComment: async (object, params, context, resolveInfo) => {
|
||||||
const { postId } = params
|
const { postId } = params
|
||||||
|
const { user, driver } = context
|
||||||
// Adding relationship from comment to post by passing in the postId,
|
// Adding relationship from comment to post by passing in the postId,
|
||||||
// but we do not want to create the comment with postId as an attribute
|
// but we do not want to create the comment with postId as an attribute
|
||||||
// because we use relationships for this. So, we are deleting it from params
|
// because we use relationships for this. So, we are deleting it from params
|
||||||
@ -12,9 +13,11 @@ export default {
|
|||||||
delete params.postId
|
delete params.postId
|
||||||
params.id = params.id || uuid()
|
params.id = params.id || uuid()
|
||||||
|
|
||||||
const session = context.driver.session()
|
const session = driver.session()
|
||||||
try {
|
|
||||||
const createCommentCypher = `
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const createCommentTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (post:Post {id: $postId})
|
MATCH (post:Post {id: $postId})
|
||||||
MATCH (author:User {id: $userId})
|
MATCH (author:User {id: $userId})
|
||||||
WITH post, author
|
WITH post, author
|
||||||
@ -23,15 +26,15 @@ export default {
|
|||||||
SET comment.updatedAt = toString(datetime())
|
SET comment.updatedAt = toString(datetime())
|
||||||
MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
|
MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
|
||||||
RETURN comment
|
RETURN comment
|
||||||
`
|
`,
|
||||||
const transactionRes = await session.run(createCommentCypher, {
|
{ userId: user.id, postId, params },
|
||||||
userId: context.user.id,
|
)
|
||||||
postId,
|
return createCommentTransactionResponse.records.map(
|
||||||
params,
|
record => record.get('comment').properties,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
const [comment] = transactionRes.records.map(record => record.get('comment').properties)
|
const [comment] = await writeTxResultPromise
|
||||||
|
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -39,15 +42,22 @@ export default {
|
|||||||
},
|
},
|
||||||
UpdateComment: async (_parent, params, context, _resolveInfo) => {
|
UpdateComment: async (_parent, params, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const updateCommentCypher = `
|
const updateCommentTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (comment:Comment {id: $params.id})
|
MATCH (comment:Comment {id: $params.id})
|
||||||
SET comment += $params
|
SET comment += $params
|
||||||
SET comment.updatedAt = toString(datetime())
|
SET comment.updatedAt = toString(datetime())
|
||||||
RETURN comment
|
RETURN comment
|
||||||
`
|
`,
|
||||||
const transactionRes = await session.run(updateCommentCypher, { params })
|
{ params },
|
||||||
const [comment] = transactionRes.records.map(record => record.get('comment').properties)
|
)
|
||||||
|
return updateCommentTransactionResponse.records.map(
|
||||||
|
record => record.get('comment').properties,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [comment] = await writeTxResultPromise
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -55,8 +65,8 @@ export default {
|
|||||||
},
|
},
|
||||||
DeleteComment: async (_parent, args, context, _resolveInfo) => {
|
DeleteComment: async (_parent, args, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const deleteCommentTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (comment:Comment {id: $commentId})
|
MATCH (comment:Comment {id: $commentId})
|
||||||
SET comment.deleted = TRUE
|
SET comment.deleted = TRUE
|
||||||
@ -66,7 +76,12 @@ export default {
|
|||||||
`,
|
`,
|
||||||
{ commentId: args.id },
|
{ commentId: args.id },
|
||||||
)
|
)
|
||||||
const [comment] = transactionRes.records.map(record => record.get('comment').properties)
|
return deleteCommentTransactionResponse.records.map(
|
||||||
|
record => record.get('comment').properties,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [comment] = await writeTxResultPromise
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -10,7 +10,8 @@ const factory = Factory()
|
|||||||
|
|
||||||
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
|
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(async () => {
|
||||||
|
await factory.cleanDatabase()
|
||||||
const { server } = createServer({
|
const { server } = createServer({
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
@ -19,8 +20,7 @@ beforeAll(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const client = createTestClient(server)
|
mutate = createTestClient(server).mutate
|
||||||
mutate = client.mutate
|
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -100,6 +100,7 @@ describe('CreateComment', () => {
|
|||||||
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
||||||
{
|
{
|
||||||
data: { CreateComment: { content: "I'm authorised to comment" } },
|
data: { CreateComment: { content: "I'm authorised to comment" } },
|
||||||
|
errors: undefined,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -108,6 +109,7 @@ describe('CreateComment', () => {
|
|||||||
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
||||||
{
|
{
|
||||||
data: { CreateComment: { author: { name: 'Author' } } },
|
data: { CreateComment: { author: { name: 'Author' } } },
|
||||||
|
errors: undefined,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -157,6 +159,7 @@ describe('UpdateComment', () => {
|
|||||||
it('updates the comment', async () => {
|
it('updates the comment', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
data: { UpdateComment: { id: 'c456', content: 'The comment is updated' } },
|
data: { UpdateComment: { id: 'c456', content: 'The comment is updated' } },
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -172,6 +175,7 @@ describe('UpdateComment', () => {
|
|||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
|
|||||||
@ -5,24 +5,29 @@ export default async function createPasswordReset(options) {
|
|||||||
const normalizedEmail = normalizeEmail(email)
|
const normalizedEmail = normalizeEmail(email)
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
const cypher = `
|
const createPasswordResetTxPromise = session.writeTransaction(async transaction => {
|
||||||
MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email})
|
const createPasswordResetTransactionResponse = await transaction.run(
|
||||||
CREATE(pr:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL})
|
|
||||||
MERGE (u)-[:REQUESTED]->(pr)
|
|
||||||
RETURN e, pr, u
|
|
||||||
`
|
`
|
||||||
const transactionRes = await session.run(cypher, {
|
MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email:$email})
|
||||||
|
CREATE(passwordReset:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL})
|
||||||
|
MERGE (user)-[:REQUESTED]->(passwordReset)
|
||||||
|
RETURN email, passwordReset, user
|
||||||
|
`,
|
||||||
|
{
|
||||||
issuedAt: issuedAt.toISOString(),
|
issuedAt: issuedAt.toISOString(),
|
||||||
nonce,
|
nonce,
|
||||||
email: normalizedEmail,
|
email: normalizedEmail,
|
||||||
})
|
},
|
||||||
const records = transactionRes.records.map(record => {
|
)
|
||||||
const { email } = record.get('e').properties
|
return createPasswordResetTransactionResponse.records.map(record => {
|
||||||
const { nonce } = record.get('pr').properties
|
const { email } = record.get('email').properties
|
||||||
const { name } = record.get('u').properties
|
const { nonce } = record.get('passwordReset').properties
|
||||||
|
const { name } = record.get('user').properties
|
||||||
return { email, nonce, name }
|
return { email, nonce, name }
|
||||||
})
|
})
|
||||||
return records[0] || {}
|
})
|
||||||
|
const [records] = await createPasswordResetTxPromise
|
||||||
|
return records || {}
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
import createPasswordReset from './createPasswordReset'
|
|
||||||
|
|
||||||
describe('createPasswordReset', () => {
|
|
||||||
const issuedAt = new Date()
|
|
||||||
const nonce = 'abcdef'
|
|
||||||
|
|
||||||
describe('email lookup', () => {
|
|
||||||
let driver
|
|
||||||
let mockSession
|
|
||||||
beforeEach(() => {
|
|
||||||
mockSession = {
|
|
||||||
close() {},
|
|
||||||
run: jest.fn().mockReturnValue({
|
|
||||||
records: {
|
|
||||||
map: jest.fn(() => []),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
driver = { session: () => mockSession }
|
|
||||||
})
|
|
||||||
|
|
||||||
it('lowercases email address', async () => {
|
|
||||||
const email = 'stRaNGeCaSiNG@ExAmplE.ORG'
|
|
||||||
await createPasswordReset({ driver, email, issuedAt, nonce })
|
|
||||||
expect(mockSession.run.mock.calls).toEqual([
|
|
||||||
[
|
|
||||||
expect.any(String),
|
|
||||||
expect.objectContaining({
|
|
||||||
email: 'strangecasing@example.org',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,25 +1,29 @@
|
|||||||
import { UserInputError } from 'apollo-server'
|
import { UserInputError } from 'apollo-server'
|
||||||
|
|
||||||
export default async function alreadyExistingMail({ args, context }) {
|
export default async function alreadyExistingMail({ args, context }) {
|
||||||
const cypher = `
|
const session = context.driver.session()
|
||||||
|
try {
|
||||||
|
const existingEmailAddressTxPromise = session.writeTransaction(async transaction => {
|
||||||
|
const existingEmailAddressTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (email:EmailAddress {email: $email})
|
MATCH (email:EmailAddress {email: $email})
|
||||||
OPTIONAL MATCH (email)-[:BELONGS_TO]-(user)
|
OPTIONAL MATCH (email)-[:BELONGS_TO]-(user)
|
||||||
RETURN email, user
|
RETURN email, user
|
||||||
`
|
`,
|
||||||
let transactionRes
|
{ email: args.email },
|
||||||
const session = context.driver.session()
|
)
|
||||||
try {
|
return existingEmailAddressTransactionResponse.records.map(record => {
|
||||||
transactionRes = await session.run(cypher, { email: args.email })
|
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
const [result] = transactionRes.records.map(record => {
|
|
||||||
return {
|
return {
|
||||||
alreadyExistingEmail: record.get('email').properties,
|
alreadyExistingEmail: record.get('email').properties,
|
||||||
user: record.get('user') && record.get('user').properties,
|
user: record.get('user') && record.get('user').properties,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { alreadyExistingEmail, user } = result || {}
|
})
|
||||||
|
const [emailBelongsToUser] = await existingEmailAddressTxPromise
|
||||||
|
const { alreadyExistingEmail, user } = emailBelongsToUser || {}
|
||||||
if (user) throw new UserInputError('A user account with this email already exists.')
|
if (user) throw new UserInputError('A user account with this email already exists.')
|
||||||
return alreadyExistingEmail
|
return alreadyExistingEmail
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,16 +76,21 @@ export default {
|
|||||||
markAsRead: async (parent, args, context, resolveInfo) => {
|
markAsRead: async (parent, args, context, resolveInfo) => {
|
||||||
const { user: currentUser } = context
|
const { user: currentUser } = context
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const cypher = `
|
const markNotificationAsReadTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
|
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
|
||||||
SET notification.read = TRUE
|
SET notification.read = TRUE
|
||||||
RETURN resource, notification, user
|
RETURN resource, notification, user
|
||||||
`
|
`,
|
||||||
const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id })
|
{ resourceId: args.id, id: currentUser.id },
|
||||||
log(result)
|
)
|
||||||
const notifications = await result.records.map(transformReturnType)
|
log(markNotificationAsReadTransactionResponse)
|
||||||
return notifications[0]
|
return markNotificationAsReadTransactionResponse.records.map(transformReturnType)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [notifications] = await writeTxResultPromise
|
||||||
|
return notifications
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,25 +12,29 @@ export default {
|
|||||||
const stillValid = new Date()
|
const stillValid = new Date()
|
||||||
stillValid.setDate(stillValid.getDate() - 1)
|
stillValid.setDate(stillValid.getDate() - 1)
|
||||||
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
|
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
|
||||||
const cypher = `
|
|
||||||
MATCH (pr:PasswordReset {nonce: $nonce})
|
|
||||||
MATCH (e:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(u:User)-[:REQUESTED]->(pr)
|
|
||||||
WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
|
|
||||||
SET pr.usedAt = datetime()
|
|
||||||
SET u.encryptedPassword = $encryptedNewPassword
|
|
||||||
RETURN pr
|
|
||||||
`
|
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(cypher, {
|
const passwordResetTxPromise = session.writeTransaction(async transaction => {
|
||||||
|
const passwordResetTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (passwordReset:PasswordReset {nonce: $nonce})
|
||||||
|
MATCH (email:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(user:User)-[:REQUESTED]->(passwordReset)
|
||||||
|
WHERE duration.between(passwordReset.issuedAt, datetime()).days <= 0 AND passwordReset.usedAt IS NULL
|
||||||
|
SET passwordReset.usedAt = datetime()
|
||||||
|
SET user.encryptedPassword = $encryptedNewPassword
|
||||||
|
RETURN passwordReset
|
||||||
|
`,
|
||||||
|
{
|
||||||
stillValid,
|
stillValid,
|
||||||
email,
|
email,
|
||||||
nonce,
|
nonce,
|
||||||
encryptedNewPassword,
|
encryptedNewPassword,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return passwordResetTransactionResponse.records.map(record => record.get('passwordReset'))
|
||||||
})
|
})
|
||||||
const [reset] = transactionRes.records.map(record => record.get('pr'))
|
const [reset] = await passwordResetTxPromise
|
||||||
const response = !!(reset && reset.properties.usedAt)
|
return !!(reset && reset.properties.usedAt)
|
||||||
return response
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,14 +14,11 @@ let authenticatedUser
|
|||||||
let variables
|
let variables
|
||||||
|
|
||||||
const getAllPasswordResets = async () => {
|
const getAllPasswordResets = async () => {
|
||||||
const session = driver.session()
|
const passwordResetQuery = await neode.cypher(
|
||||||
try {
|
'MATCH (passwordReset:PasswordReset) RETURN passwordReset',
|
||||||
const transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r')
|
)
|
||||||
const resets = transactionRes.records.map(record => record.get('r'))
|
const resets = passwordResetQuery.records.map(record => record.get('passwordReset'))
|
||||||
return resets
|
return resets
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -57,17 +57,20 @@ export default {
|
|||||||
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
||||||
const { postId, data } = params
|
const { postId, data } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const readTxResultPromise = session.readTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const emotionsCountTransactionResponse = await transaction.run(
|
||||||
`MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
`
|
||||||
|
MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
||||||
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
||||||
`,
|
`,
|
||||||
{ postId, data },
|
{ postId, data },
|
||||||
)
|
)
|
||||||
|
return emotionsCountTransactionResponse.records.map(
|
||||||
const [emotionsCount] = transactionRes.records.map(record => {
|
record => record.get('emotionsCount').low,
|
||||||
return record.get('emotionsCount').low
|
)
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [emotionsCount] = await readTxResultPromise
|
||||||
return emotionsCount
|
return emotionsCount
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -76,16 +79,18 @@ export default {
|
|||||||
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
|
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
|
||||||
const { postId } = params
|
const { postId } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const readTxResultPromise = session.readTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const emotionsTransactionResponse = await transaction.run(
|
||||||
`MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
`
|
||||||
RETURN collect(emoted.emotion) as emotion`,
|
MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
||||||
|
RETURN collect(emoted.emotion) as emotion
|
||||||
|
`,
|
||||||
{ userId: context.user.id, postId },
|
{ userId: context.user.id, postId },
|
||||||
)
|
)
|
||||||
|
return emotionsTransactionResponse.records.map(record => record.get('emotion'))
|
||||||
const [emotions] = transactionRes.records.map(record => {
|
|
||||||
return record.get('emotion')
|
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [emotions] = await readTxResultPromise
|
||||||
return emotions
|
return emotions
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -98,7 +103,11 @@ export default {
|
|||||||
delete params.categoryIds
|
delete params.categoryIds
|
||||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||||
params.id = params.id || uuid()
|
params.id = params.id || uuid()
|
||||||
const createPostCypher = `CREATE (post:Post {params})
|
const session = context.driver.session()
|
||||||
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const createPostTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
CREATE (post:Post {params})
|
||||||
SET post.createdAt = toString(datetime())
|
SET post.createdAt = toString(datetime())
|
||||||
SET post.updatedAt = toString(datetime())
|
SET post.updatedAt = toString(datetime())
|
||||||
WITH post
|
WITH post
|
||||||
@ -108,15 +117,15 @@ export default {
|
|||||||
UNWIND $categoryIds AS categoryId
|
UNWIND $categoryIds AS categoryId
|
||||||
MATCH (category:Category {id: categoryId})
|
MATCH (category:Category {id: categoryId})
|
||||||
MERGE (post)-[:CATEGORIZED]->(category)
|
MERGE (post)-[:CATEGORIZED]->(category)
|
||||||
RETURN post`
|
RETURN post
|
||||||
|
`,
|
||||||
const createPostVariables = { userId: context.user.id, categoryIds, params }
|
{ userId: context.user.id, categoryIds, params },
|
||||||
|
)
|
||||||
const session = context.driver.session()
|
return createPostTransactionResponse.records.map(record => record.get('post').properties)
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(createPostCypher, createPostVariables)
|
const [post] = await writeTxResultPromise
|
||||||
const posts = transactionRes.records.map(record => record.get('post').properties)
|
return post
|
||||||
return posts[0]
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||||
throw new UserInputError('Post with this slug already exists!')
|
throw new UserInputError('Post with this slug already exists!')
|
||||||
@ -129,14 +138,14 @@ export default {
|
|||||||
const { categoryIds } = params
|
const { categoryIds } = params
|
||||||
delete params.categoryIds
|
delete params.categoryIds
|
||||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||||
let updatePostCypher = `MATCH (post:Post {id: $params.id})
|
const session = context.driver.session()
|
||||||
|
let updatePostCypher = `
|
||||||
|
MATCH (post:Post {id: $params.id})
|
||||||
SET post += $params
|
SET post += $params
|
||||||
SET post.updatedAt = toString(datetime())
|
SET post.updatedAt = toString(datetime())
|
||||||
WITH post
|
WITH post
|
||||||
`
|
`
|
||||||
|
|
||||||
const session = context.driver.session()
|
|
||||||
try {
|
|
||||||
if (categoryIds && categoryIds.length) {
|
if (categoryIds && categoryIds.length) {
|
||||||
const cypherDeletePreviousRelations = `
|
const cypherDeletePreviousRelations = `
|
||||||
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
||||||
@ -144,7 +153,9 @@ export default {
|
|||||||
RETURN post, category
|
RETURN post, category
|
||||||
`
|
`
|
||||||
|
|
||||||
await session.run(cypherDeletePreviousRelations, { params })
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(cypherDeletePreviousRelations, { params })
|
||||||
|
})
|
||||||
|
|
||||||
updatePostCypher += `
|
updatePostCypher += `
|
||||||
UNWIND $categoryIds AS categoryId
|
UNWIND $categoryIds AS categoryId
|
||||||
@ -156,11 +167,15 @@ export default {
|
|||||||
|
|
||||||
updatePostCypher += `RETURN post`
|
updatePostCypher += `RETURN post`
|
||||||
const updatePostVariables = { categoryIds, params }
|
const updatePostVariables = { categoryIds, params }
|
||||||
|
try {
|
||||||
const transactionRes = await session.run(updatePostCypher, updatePostVariables)
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const [post] = transactionRes.records.map(record => {
|
const updatePostTransactionResponse = await transaction.run(
|
||||||
return record.get('post').properties
|
updatePostCypher,
|
||||||
|
updatePostVariables,
|
||||||
|
)
|
||||||
|
return updatePostTransactionResponse.records.map(record => record.get('post').properties)
|
||||||
})
|
})
|
||||||
|
const [post] = await writeTxResultPromise
|
||||||
return post
|
return post
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -169,9 +184,8 @@ export default {
|
|||||||
|
|
||||||
DeletePost: async (object, args, context, resolveInfo) => {
|
DeletePost: async (object, args, context, resolveInfo) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
// we cannot set slug to 'UNAVAILABE' because of unique constraints
|
const deletePostTransactionResponse = await transaction.run(
|
||||||
const transactionRes = await session.run(
|
|
||||||
`
|
`
|
||||||
MATCH (post:Post {id: $postId})
|
MATCH (post:Post {id: $postId})
|
||||||
OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment)
|
OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment)
|
||||||
@ -185,7 +199,10 @@ export default {
|
|||||||
`,
|
`,
|
||||||
{ postId: args.id },
|
{ postId: args.id },
|
||||||
)
|
)
|
||||||
const [post] = transactionRes.records.map(record => record.get('post').properties)
|
return deletePostTransactionResponse.records.map(record => record.get('post').properties)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [post] = await writeTxResultPromise
|
||||||
return post
|
return post
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -195,21 +212,24 @@ export default {
|
|||||||
const { to, data } = params
|
const { to, data } = params
|
||||||
const { user } = context
|
const { user } = context
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const addPostEmotionsTransactionResponse = await transaction.run(
|
||||||
`MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
|
`
|
||||||
|
MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
|
||||||
MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
|
MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
|
||||||
RETURN userFrom, postTo, emotedRelation`,
|
RETURN userFrom, postTo, emotedRelation`,
|
||||||
{ user, to, data },
|
{ user, to, data },
|
||||||
)
|
)
|
||||||
|
return addPostEmotionsTransactionResponse.records.map(record => {
|
||||||
const [emoted] = transactionRes.records.map(record => {
|
|
||||||
return {
|
return {
|
||||||
from: { ...record.get('userFrom').properties },
|
from: { ...record.get('userFrom').properties },
|
||||||
to: { ...record.get('postTo').properties },
|
to: { ...record.get('postTo').properties },
|
||||||
...record.get('emotedRelation').properties,
|
...record.get('emotedRelation').properties,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [emoted] = await writeTxResultPromise
|
||||||
return emoted
|
return emoted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -219,20 +239,25 @@ export default {
|
|||||||
const { to, data } = params
|
const { to, data } = params
|
||||||
const { id: from } = context.user
|
const { id: from } = context.user
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const removePostEmotionsTransactionResponse = await transaction.run(
|
||||||
`MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
|
`
|
||||||
|
MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
|
||||||
DELETE emotedRelation
|
DELETE emotedRelation
|
||||||
RETURN userFrom, postTo`,
|
RETURN userFrom, postTo
|
||||||
|
`,
|
||||||
{ from, to, data },
|
{ from, to, data },
|
||||||
)
|
)
|
||||||
const [emoted] = transactionRes.records.map(record => {
|
return removePostEmotionsTransactionResponse.records.map(record => {
|
||||||
return {
|
return {
|
||||||
from: { ...record.get('userFrom').properties },
|
from: { ...record.get('userFrom').properties },
|
||||||
to: { ...record.get('postTo').properties },
|
to: { ...record.get('postTo').properties },
|
||||||
emotion: data.emotion,
|
emotion: data.emotion,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [emoted] = await writeTxResultPromise
|
||||||
return emoted
|
return emoted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -344,21 +369,28 @@ export default {
|
|||||||
relatedContributions: async (parent, params, context, resolveInfo) => {
|
relatedContributions: async (parent, params, context, resolveInfo) => {
|
||||||
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
|
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
|
||||||
const { id } = parent
|
const { id } = parent
|
||||||
const statement = `
|
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)
|
MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||||
WHERE NOT post.deleted AND NOT post.disabled
|
WHERE NOT post.deleted AND NOT post.disabled
|
||||||
RETURN DISTINCT post
|
RETURN DISTINCT post
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
`
|
`,
|
||||||
let relatedContributions
|
{ id },
|
||||||
const session = context.driver.session()
|
)
|
||||||
|
return relatedContributionsTransactionResponse.records.map(
|
||||||
|
record => record.get('post').properties,
|
||||||
|
)
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const result = await session.run(statement, { id })
|
const relatedContributions = await writeTxResultPromise
|
||||||
relatedContributions = result.records.map(r => r.get('post').properties)
|
return relatedContributions
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
return relatedContributions
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -383,7 +383,10 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('updates a post', async () => {
|
it('updates a post', async () => {
|
||||||
const expected = { data: { UpdatePost: { id: 'p9876', content: 'New content' } } }
|
const expected = {
|
||||||
|
data: { UpdatePost: { id: 'p9876', content: 'New content' } },
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
@ -394,6 +397,7 @@ describe('UpdatePost', () => {
|
|||||||
data: {
|
data: {
|
||||||
UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) },
|
UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) },
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -421,6 +425,7 @@ describe('UpdatePost', () => {
|
|||||||
categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]),
|
categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -441,6 +446,7 @@ describe('UpdatePost', () => {
|
|||||||
categories: expect.arrayContaining([{ id: 'cat27' }]),
|
categories: expect.arrayContaining([{ id: 'cat27' }]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -722,6 +728,7 @@ describe('UpdatePost', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
|
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
|
||||||
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
||||||
|
|||||||
@ -24,8 +24,8 @@ export default {
|
|||||||
const { user } = await getUserAndBadge(params)
|
const { user } = await getUserAndBadge(params)
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
// silly neode cannot remove relationships
|
await session.writeTransaction(transaction => {
|
||||||
await session.run(
|
return transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
|
MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
|
||||||
DELETE reward
|
DELETE reward
|
||||||
@ -36,6 +36,7 @@ export default {
|
|||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
shout: async (_object, params, context, _resolveInfo) => {
|
shout: async (_object, params, context, _resolveInfo) => {
|
||||||
@ -5,22 +7,24 @@ export default {
|
|||||||
|
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(
|
const shoutWriteTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
`MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
|
const shoutTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
|
||||||
WHERE $type IN labels(node) AND NOT userWritten.id = $userId
|
WHERE $type IN labels(node) AND NOT userWritten.id = $userId
|
||||||
MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node)
|
MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node)
|
||||||
RETURN COUNT(relation) > 0 as isShouted`,
|
RETURN COUNT(relation) > 0 as isShouted
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
userId: context.user.id,
|
userId: context.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
log(shoutTransactionResponse)
|
||||||
const [isShouted] = transactionRes.records.map(record => {
|
return shoutTransactionResponse.records.map(record => record.get('isShouted'))
|
||||||
return record.get('isShouted')
|
|
||||||
})
|
})
|
||||||
|
const [isShouted] = await shoutWriteTxResultPromise
|
||||||
return isShouted
|
return isShouted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -31,20 +35,24 @@ export default {
|
|||||||
const { id, type } = params
|
const { id, type } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(
|
const unshoutWriteTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
`MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
|
const unshoutTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
|
||||||
WHERE $type IN labels(node)
|
WHERE $type IN labels(node)
|
||||||
DELETE relation
|
DELETE relation
|
||||||
RETURN COUNT(relation) > 0 as isShouted`,
|
RETURN COUNT(relation) > 0 as isShouted
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
userId: context.user.id,
|
userId: context.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const [isShouted] = transactionRes.records.map(record => {
|
log(unshoutTransactionResponse)
|
||||||
return record.get('isShouted')
|
return unshoutTransactionResponse.records.map(record => record.get('isShouted'))
|
||||||
})
|
})
|
||||||
|
const [isShouted] = await unshoutWriteTxResultPromise
|
||||||
return isShouted
|
return isShouted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
statistics: async (_parent, _args, { driver }) => {
|
statistics: async (_parent, _args, { driver }) => {
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const response = {}
|
const counts = {}
|
||||||
try {
|
try {
|
||||||
const mapping = {
|
const mapping = {
|
||||||
countUsers: 'User',
|
countUsers: 'User',
|
||||||
@ -13,27 +15,28 @@ export default {
|
|||||||
countFollows: 'FOLLOWS',
|
countFollows: 'FOLLOWS',
|
||||||
countShouts: 'SHOUTED',
|
countShouts: 'SHOUTED',
|
||||||
}
|
}
|
||||||
const cypher = `
|
const statisticsReadTxResultPromise = session.readTransaction(async transaction => {
|
||||||
|
const statisticsTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
CALL apoc.meta.stats() YIELD labels, relTypesCount
|
CALL apoc.meta.stats() YIELD labels, relTypesCount
|
||||||
RETURN labels, relTypesCount
|
RETURN labels, relTypesCount
|
||||||
`
|
`,
|
||||||
const result = await session.run(cypher)
|
)
|
||||||
const [statistics] = await result.records.map(record => {
|
log(statisticsTransactionResponse)
|
||||||
|
return statisticsTransactionResponse.records.map(record => {
|
||||||
return {
|
return {
|
||||||
...record.get('labels'),
|
...record.get('labels'),
|
||||||
...record.get('relTypesCount'),
|
...record.get('relTypesCount'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
const [statistics] = await statisticsReadTxResultPromise
|
||||||
Object.keys(mapping).forEach(key => {
|
Object.keys(mapping).forEach(key => {
|
||||||
const stat = statistics[mapping[key]]
|
const stat = statistics[mapping[key]]
|
||||||
response[key] = stat ? stat.toNumber() : 0
|
counts[key] = stat ? stat.toNumber() : 0
|
||||||
})
|
})
|
||||||
|
counts.countInvites = counts.countEmails - counts.countUsers
|
||||||
/*
|
return counts
|
||||||
* Note: invites count is calculated this way because invitation codes are not in use yet
|
|
||||||
*/
|
|
||||||
response.countInvites = response.countEmails - response.countUsers
|
|
||||||
return response
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import bcrypt from 'bcryptjs'
|
|||||||
import { AuthenticationError } from 'apollo-server'
|
import { AuthenticationError } from 'apollo-server'
|
||||||
import { getNeode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
import normalizeEmail from './helpers/normalizeEmail'
|
import normalizeEmail from './helpers/normalizeEmail'
|
||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
|
|
||||||
@ -25,17 +26,18 @@ export default {
|
|||||||
email = normalizeEmail(email)
|
email = normalizeEmail(email)
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
const result = await session.run(
|
const loginReadTxResultPromise = session.readTransaction(async transaction => {
|
||||||
|
const loginTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail})
|
MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail})
|
||||||
RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1
|
RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1
|
||||||
`,
|
`,
|
||||||
{ userEmail: email },
|
{ userEmail: email },
|
||||||
)
|
)
|
||||||
const [currentUser] = await result.records.map(record => {
|
log(loginTransactionResponse)
|
||||||
return record.get('user')
|
return loginTransactionResponse.records.map(record => record.get('user'))
|
||||||
})
|
})
|
||||||
|
const [currentUser] = await loginReadTxResultPromise
|
||||||
if (
|
if (
|
||||||
currentUser &&
|
currentUser &&
|
||||||
(await bcrypt.compareSync(password, currentUser.encryptedPassword)) &&
|
(await bcrypt.compareSync(password, currentUser.encryptedPassword)) &&
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import fileUpload from './fileUpload'
|
|||||||
import { getNeode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
import { UserInputError, ForbiddenError } from 'apollo-server'
|
import { UserInputError, ForbiddenError } from 'apollo-server'
|
||||||
import Resolver from './helpers/Resolver'
|
import Resolver from './helpers/Resolver'
|
||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
|
|
||||||
@ -100,35 +101,47 @@ export default {
|
|||||||
const blockedUser = await neode.find('User', args.id)
|
const blockedUser = await neode.find('User', args.id)
|
||||||
return blockedUser.toJson()
|
return blockedUser.toJson()
|
||||||
},
|
},
|
||||||
UpdateUser: async (object, args, context, resolveInfo) => {
|
UpdateUser: async (_parent, params, context, _resolveInfo) => {
|
||||||
const { termsAndConditionsAgreedVersion } = args
|
const { termsAndConditionsAgreedVersion } = params
|
||||||
if (termsAndConditionsAgreedVersion) {
|
if (termsAndConditionsAgreedVersion) {
|
||||||
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
||||||
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
||||||
throw new ForbiddenError('Invalid version format!')
|
throw new ForbiddenError('Invalid version format!')
|
||||||
}
|
}
|
||||||
args.termsAndConditionsAgreedAt = new Date().toISOString()
|
params.termsAndConditionsAgreedAt = new Date().toISOString()
|
||||||
}
|
}
|
||||||
args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
|
params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
|
||||||
|
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 },
|
||||||
|
)
|
||||||
|
return updateUserTransactionResponse.records.map(record => record.get('user').properties)
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const user = await neode.find('User', args.id)
|
const [user] = await writeTxResultPromise
|
||||||
if (!user) return null
|
return user
|
||||||
await user.update({ ...args, updatedAt: new Date().toISOString() })
|
} catch (error) {
|
||||||
return user.toJson()
|
throw new UserInputError(error.message)
|
||||||
} catch (e) {
|
} finally {
|
||||||
throw new UserInputError(e.message)
|
session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DeleteUser: async (object, params, context, resolveInfo) => {
|
DeleteUser: async (object, params, context, resolveInfo) => {
|
||||||
const { resource } = params
|
const { resource } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
|
|
||||||
let user
|
|
||||||
try {
|
try {
|
||||||
if (resource && resource.length) {
|
if (resource && resource.length) {
|
||||||
await Promise.all(
|
await session.writeTransaction(transaction => {
|
||||||
resource.map(async node => {
|
resource.map(node => {
|
||||||
await session.run(
|
return transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
||||||
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
|
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
|
||||||
@ -136,17 +149,18 @@ export default {
|
|||||||
SET resource.content = 'UNAVAILABLE'
|
SET resource.content = 'UNAVAILABLE'
|
||||||
SET resource.contentExcerpt = 'UNAVAILABLE'
|
SET resource.contentExcerpt = 'UNAVAILABLE'
|
||||||
SET comment.deleted = true
|
SET comment.deleted = true
|
||||||
RETURN author`,
|
RETURN author
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
userId: context.user.id,
|
userId: context.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}),
|
})
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// we cannot set slug to 'UNAVAILABE' because of unique constraints
|
const deleteUserTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionResult = await session.run(
|
const deleteUserTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (user:User {id: $userId})
|
MATCH (user:User {id: $userId})
|
||||||
SET user.deleted = true
|
SET user.deleted = true
|
||||||
@ -158,14 +172,18 @@ export default {
|
|||||||
WITH user
|
WITH user
|
||||||
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
|
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
|
||||||
DETACH DELETE socialMedia
|
DETACH DELETE socialMedia
|
||||||
RETURN user`,
|
RETURN user
|
||||||
|
`,
|
||||||
{ userId: context.user.id },
|
{ userId: context.user.id },
|
||||||
)
|
)
|
||||||
user = transactionResult.records.map(r => r.get('user').properties)[0]
|
log(deleteUserTransactionResponse)
|
||||||
|
return deleteUserTransactionResponse.records.map(record => record.get('user').properties)
|
||||||
|
})
|
||||||
|
const [user] = await deleteUserTxResultPromise
|
||||||
|
return user
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
return user
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
User: {
|
User: {
|
||||||
|
|||||||
@ -68,6 +68,7 @@ describe('User', () => {
|
|||||||
it('is permitted', async () => {
|
it('is permitted', async () => {
|
||||||
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
|
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
|
||||||
data: { User: [{ name: 'Johnny' }] },
|
data: { User: [{ name: 'Johnny' }] },
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -90,8 +91,7 @@ describe('User', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('UpdateUser', () => {
|
describe('UpdateUser', () => {
|
||||||
let userParams
|
let userParams, variables
|
||||||
let variables
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
userParams = {
|
userParams = {
|
||||||
@ -111,16 +111,23 @@ describe('UpdateUser', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updateUserMutation = gql`
|
const updateUserMutation = gql`
|
||||||
mutation($id: ID!, $name: String, $termsAndConditionsAgreedVersion: String) {
|
mutation(
|
||||||
|
$id: ID!
|
||||||
|
$name: String
|
||||||
|
$termsAndConditionsAgreedVersion: String
|
||||||
|
$locationName: String
|
||||||
|
) {
|
||||||
UpdateUser(
|
UpdateUser(
|
||||||
id: $id
|
id: $id
|
||||||
name: $name
|
name: $name
|
||||||
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
||||||
|
locationName: $locationName
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
termsAndConditionsAgreedVersion
|
termsAndConditionsAgreedVersion
|
||||||
termsAndConditionsAgreedAt
|
termsAndConditionsAgreedAt
|
||||||
|
locationName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -152,7 +159,7 @@ describe('UpdateUser', () => {
|
|||||||
authenticatedUser = await user.toJson()
|
authenticatedUser = await user.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('name within specifications', async () => {
|
it('updates the name', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
data: {
|
data: {
|
||||||
UpdateUser: {
|
UpdateUser: {
|
||||||
@ -160,36 +167,13 @@ describe('UpdateUser', () => {
|
|||||||
name: 'John Doughnut',
|
name: 'John Doughnut',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with `null` as name', async () => {
|
|
||||||
const variables = {
|
|
||||||
id: 'u47',
|
|
||||||
name: null,
|
|
||||||
}
|
|
||||||
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
|
||||||
expect(errors[0]).toHaveProperty(
|
|
||||||
'message',
|
|
||||||
'child "name" fails because ["name" contains an invalid value, "name" must be a string]',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('with too short name', async () => {
|
|
||||||
const variables = {
|
|
||||||
id: 'u47',
|
|
||||||
name: ' ',
|
|
||||||
}
|
|
||||||
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
|
||||||
expect(errors[0]).toHaveProperty(
|
|
||||||
'message',
|
|
||||||
'child "name" fails because ["name" length must be at least 3 characters long]',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given a new agreed version of terms and conditions', () => {
|
describe('given a new agreed version of terms and conditions', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' }
|
variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' }
|
||||||
@ -202,6 +186,7 @@ describe('UpdateUser', () => {
|
|||||||
termsAndConditionsAgreedAt: expect.any(String),
|
termsAndConditionsAgreedAt: expect.any(String),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
||||||
@ -222,6 +207,7 @@ describe('UpdateUser', () => {
|
|||||||
termsAndConditionsAgreedAt: null,
|
termsAndConditionsAgreedAt: null,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
||||||
@ -238,6 +224,14 @@ describe('UpdateUser', () => {
|
|||||||
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
||||||
expect(errors[0]).toHaveProperty('message', 'Invalid version format!')
|
expect(errors[0]).toHaveProperty('message', 'Invalid version format!')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('supports updating location', async () => {
|
||||||
|
variables = { ...variables, locationName: 'Hamburg, New Jersey, United States of America' }
|
||||||
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
|
||||||
|
data: { UpdateUser: { locationName: 'Hamburg, New Jersey, United States of America' } },
|
||||||
|
errors: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -372,6 +366,7 @@ describe('DeleteUser', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: deleteUserMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: deleteUserMutation, variables })).resolves.toMatchObject(
|
||||||
expectedResponse,
|
expectedResponse,
|
||||||
@ -418,6 +413,7 @@ describe('DeleteUser', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(
|
await expect(
|
||||||
mutate({ mutation: deleteUserMutation, variables }),
|
mutate({ mutation: deleteUserMutation, variables }),
|
||||||
@ -465,6 +461,7 @@ describe('DeleteUser', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(
|
await expect(
|
||||||
mutate({ mutation: deleteUserMutation, variables }),
|
mutate({ mutation: deleteUserMutation, variables }),
|
||||||
@ -511,6 +508,7 @@ describe('DeleteUser', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(
|
await expect(
|
||||||
mutate({ mutation: deleteUserMutation, variables }),
|
mutate({ mutation: deleteUserMutation, variables }),
|
||||||
|
|||||||
@ -26,7 +26,7 @@ enum _UserOrdering {
|
|||||||
type User {
|
type User {
|
||||||
id: ID!
|
id: ID!
|
||||||
actorId: String
|
actorId: String
|
||||||
name: String
|
name: String!
|
||||||
email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email")
|
email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email")
|
||||||
slug: String!
|
slug: String!
|
||||||
avatar: String
|
avatar: String
|
||||||
|
|||||||
@ -29,10 +29,16 @@ const factories = {
|
|||||||
|
|
||||||
export const cleanDatabase = async (options = {}) => {
|
export const cleanDatabase = async (options = {}) => {
|
||||||
const { driver = getDriver() } = options
|
const { driver = getDriver() } = options
|
||||||
const cypher = 'MATCH (n) DETACH DELETE n'
|
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
return await session.run(cypher)
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (everything)
|
||||||
|
DETACH DELETE everything
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
import { allowEmbedIframesMutation } from '~/graphql/User.js'
|
import { updateUserMutation } from '~/graphql/User.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'embed-component',
|
name: 'embed-component',
|
||||||
@ -129,7 +129,7 @@ export default {
|
|||||||
async updateEmbedSettings(allowEmbedIframes) {
|
async updateEmbedSettings(allowEmbedIframes) {
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: allowEmbedIframesMutation(),
|
mutation: updateUserMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
id: this.currentUser.id,
|
id: this.currentUser.id,
|
||||||
allowEmbedIframes,
|
allowEmbedIframes,
|
||||||
|
|||||||
@ -33,12 +33,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import gql from 'graphql-tag'
|
|
||||||
import Dropdown from '~/components/Dropdown'
|
import Dropdown from '~/components/Dropdown'
|
||||||
import find from 'lodash/find'
|
import find from 'lodash/find'
|
||||||
import orderBy from 'lodash/orderBy'
|
import orderBy from 'lodash/orderBy'
|
||||||
import locales from '~/locales'
|
import locales from '~/locales'
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
|
import { updateUserMutation } from '~/graphql/User.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -87,14 +87,7 @@ export default {
|
|||||||
if (!this.currentUser || !this.currentUser.id) return null
|
if (!this.currentUser || !this.currentUser.id) return null
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: gql`
|
mutation: updateUserMutation(),
|
||||||
mutation($id: ID!, $locale: String) {
|
|
||||||
UpdateUser(id: $id, locale: $locale) {
|
|
||||||
id
|
|
||||||
locale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
variables: {
|
||||||
id: this.currentUser.id,
|
id: this.currentUser.id,
|
||||||
locale: this.$i18n.locale(),
|
locale: this.$i18n.locale(),
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import vueDropzone from 'nuxt-dropzone'
|
import vueDropzone from 'nuxt-dropzone'
|
||||||
import gql from 'graphql-tag'
|
import { updateUserMutation } from '~/graphql/User.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -62,14 +62,7 @@ export default {
|
|||||||
const avatarUpload = file[0]
|
const avatarUpload = file[0]
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: gql`
|
mutation: updateUserMutation(),
|
||||||
mutation($id: ID!, $avatarUpload: Upload) {
|
|
||||||
UpdateUser(id: $id, avatarUpload: $avatarUpload) {
|
|
||||||
id
|
|
||||||
avatar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
variables: {
|
||||||
avatarUpload,
|
avatarUpload,
|
||||||
id: this.user.id,
|
id: this.user.id,
|
||||||
|
|||||||
@ -140,23 +140,42 @@ export const unfollowUserMutation = i18n => {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const allowEmbedIframesMutation = () => {
|
export const updateUserMutation = () => {
|
||||||
return gql`
|
return gql`
|
||||||
mutation($id: ID!, $allowEmbedIframes: Boolean) {
|
mutation(
|
||||||
UpdateUser(id: $id, allowEmbedIframes: $allowEmbedIframes) {
|
$id: ID!
|
||||||
|
$slug: String
|
||||||
|
$name: String
|
||||||
|
$locationName: String
|
||||||
|
$about: String
|
||||||
|
$allowEmbedIframes: Boolean
|
||||||
|
$showShoutsPublicly: Boolean
|
||||||
|
$locale: String
|
||||||
|
$termsAndConditionsAgreedVersion: String
|
||||||
|
$avatarUpload: Upload
|
||||||
|
) {
|
||||||
|
UpdateUser(
|
||||||
|
id: $id
|
||||||
|
slug: $slug
|
||||||
|
name: $name
|
||||||
|
locationName: $locationName
|
||||||
|
about: $about
|
||||||
|
allowEmbedIframes: $allowEmbedIframes
|
||||||
|
showShoutsPublicly: $showShoutsPublicly
|
||||||
|
locale: $locale
|
||||||
|
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
||||||
|
avatarUpload: $avatarUpload
|
||||||
|
) {
|
||||||
id
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
locationName
|
||||||
|
about
|
||||||
allowEmbedIframes
|
allowEmbedIframes
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const showShoutsPubliclyMutation = () => {
|
|
||||||
return gql`
|
|
||||||
mutation($id: ID!, $showShoutsPublicly: Boolean) {
|
|
||||||
UpdateUser(id: $id, showShoutsPublicly: $showShoutsPublicly) {
|
|
||||||
id
|
|
||||||
showShoutsPublicly
|
showShoutsPublicly
|
||||||
|
locale
|
||||||
|
termsAndConditionsAgreedVersion
|
||||||
|
avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -169,14 +188,3 @@ export const checkSlugAvailableQuery = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const localeMutation = () => {
|
|
||||||
return gql`
|
|
||||||
mutation($id: ID!, $locale: String) {
|
|
||||||
UpdateUser(id: $id, locale: $locale) {
|
|
||||||
id
|
|
||||||
locale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
import { allowEmbedIframesMutation } from '~/graphql/User.js'
|
import { updateUserMutation } from '~/graphql/User.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
head() {
|
head() {
|
||||||
@ -69,7 +69,7 @@ export default {
|
|||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: allowEmbedIframesMutation(),
|
mutation: updateUserMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
id: this.currentUser.id,
|
id: this.currentUser.id,
|
||||||
allowEmbedIframes: !this.disabled,
|
allowEmbedIframes: !this.disabled,
|
||||||
|
|||||||
@ -41,40 +41,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import gql from 'graphql-tag'
|
|
||||||
|
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
import { CancelToken } from 'axios'
|
import { CancelToken } from 'axios'
|
||||||
import UniqueSlugForm from '~/components/utils/UniqueSlugForm'
|
import UniqueSlugForm from '~/components/utils/UniqueSlugForm'
|
||||||
|
import { updateUserMutation } from '~/graphql/User'
|
||||||
|
|
||||||
let timeout
|
let timeout
|
||||||
const mapboxToken = process.env.MAPBOX_TOKEN
|
const mapboxToken = process.env.MAPBOX_TOKEN
|
||||||
|
|
||||||
/*
|
|
||||||
const query = gql`
|
|
||||||
query getUser($id: ID) {
|
|
||||||
User(id: $id) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
locationName
|
|
||||||
about
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
*/
|
|
||||||
|
|
||||||
const mutation = gql`
|
|
||||||
mutation($id: ID!, $slug: String, $name: String, $locationName: String, $about: String) {
|
|
||||||
UpdateUser(id: $id, slug: $slug, name: $name, locationName: $locationName, about: $about) {
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
name
|
|
||||||
locationName
|
|
||||||
about
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -120,7 +94,7 @@ export default {
|
|||||||
locationName = locationName && (locationName.label || locationName)
|
locationName = locationName && (locationName.label || locationName)
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation,
|
mutation: updateUserMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
id: this.currentUser.id,
|
id: this.currentUser.id,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
import { showShoutsPubliclyMutation } from '~/graphql/User'
|
import { updateUserMutation } from '~/graphql/User'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@ -36,7 +36,7 @@ export default {
|
|||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: showShoutsPubliclyMutation(),
|
mutation: updateUserMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
id: this.currentUser.id,
|
id: this.currentUser.id,
|
||||||
showShoutsPublicly: this.shoutsAllowed,
|
showShoutsPublicly: this.shoutsAllowed,
|
||||||
|
|||||||
@ -24,17 +24,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import gql from 'graphql-tag'
|
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
||||||
const mutation = gql`
|
import { updateUserMutation } from '~/graphql/User.js'
|
||||||
mutation($id: ID!, $termsAndConditionsAgreedVersion: String) {
|
|
||||||
UpdateUser(id: $id, termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion) {
|
|
||||||
id
|
|
||||||
termsAndConditionsAgreedVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
export default {
|
export default {
|
||||||
layout: 'default',
|
layout: 'default',
|
||||||
head() {
|
head() {
|
||||||
@ -74,7 +67,7 @@ export default {
|
|||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation,
|
mutation: updateUserMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
id: this.currentUser.id,
|
id: this.currentUser.id,
|
||||||
termsAndConditionsAgreedVersion: VERSION,
|
termsAndConditionsAgreedVersion: VERSION,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user