mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #1139 from Human-Connection/refactor-social-media-backend
Refactor social media backend
This commit is contained in:
commit
06c14fcf6f
@ -1,4 +1,7 @@
|
||||
import { rule, shield, deny, allow, and, or, not } from 'graphql-shield'
|
||||
import { neode } from '../bootstrap/neo4j'
|
||||
|
||||
const instance = neode()
|
||||
|
||||
/*
|
||||
* TODO: implement
|
||||
@ -7,7 +10,7 @@ import { rule, shield, deny, allow, and, or, not } from 'graphql-shield'
|
||||
const isAuthenticated = rule({
|
||||
cache: 'contextual',
|
||||
})(async (_parent, _args, ctx, _info) => {
|
||||
return ctx.user !== null
|
||||
return ctx.user != null
|
||||
})
|
||||
|
||||
const isModerator = rule()(async (parent, args, { user }, info) => {
|
||||
@ -30,6 +33,14 @@ const isMyOwn = rule({
|
||||
return context.user.id === parent.id
|
||||
})
|
||||
|
||||
const isMySocialMedia = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_, args, { user }) => {
|
||||
let socialMedia = await instance.find('SocialMedia', args.id)
|
||||
socialMedia = await socialMedia.toJson()
|
||||
return socialMedia.ownedBy.node.id === user.id
|
||||
})
|
||||
|
||||
const belongsToMe = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_, args, context) => {
|
||||
@ -163,8 +174,8 @@ const permissions = shield(
|
||||
DeletePost: isAuthor,
|
||||
report: isAuthenticated,
|
||||
CreateSocialMedia: isAuthenticated,
|
||||
UpdateSocialMedia: isAuthenticated,
|
||||
DeleteSocialMedia: isAuthenticated,
|
||||
UpdateSocialMedia: isMySocialMedia,
|
||||
DeleteSocialMedia: isMySocialMedia,
|
||||
// AddBadgeRewarded: isAdmin,
|
||||
// RemoveBadgeRewarded: isAdmin,
|
||||
reward: isAdmin,
|
||||
|
||||
@ -1,23 +1,8 @@
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import Joi from '@hapi/joi'
|
||||
|
||||
const COMMENT_MIN_LENGTH = 1
|
||||
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
|
||||
|
||||
const validate = schema => {
|
||||
return async (resolve, root, args, context, info) => {
|
||||
const validation = schema.validate(args)
|
||||
if (validation.error) throw new UserInputError(validation.error)
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
}
|
||||
|
||||
const socialMediaSchema = Joi.object().keys({
|
||||
url: Joi.string()
|
||||
.uri()
|
||||
.required(),
|
||||
})
|
||||
|
||||
const validateCommentCreation = async (resolve, root, args, context, info) => {
|
||||
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
||||
const { postId } = args
|
||||
@ -57,7 +42,6 @@ const validateUpdateComment = async (resolve, root, args, context, info) => {
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateSocialMedia: validate(socialMediaSchema),
|
||||
CreateComment: validateCommentCreation,
|
||||
UpdateComment: validateUpdateComment,
|
||||
},
|
||||
|
||||
15
backend/src/models/SocialMedia.js
Normal file
15
backend/src/models/SocialMedia.js
Normal file
@ -0,0 +1,15 @@
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
module.exports = {
|
||||
id: { type: 'string', primary: true, default: uuid },
|
||||
url: { type: 'string', uri: true, required: true },
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
ownedBy: {
|
||||
type: 'relationship',
|
||||
relationship: 'OWNED_BY',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
eager: true,
|
||||
cascade: 'detach',
|
||||
},
|
||||
}
|
||||
@ -5,4 +5,5 @@ export default {
|
||||
User: require('./User.js'),
|
||||
InvitationCode: require('./InvitationCode.js'),
|
||||
EmailAddress: require('./EmailAddress.js'),
|
||||
SocialMedia: require('./SocialMedia.js'),
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ export default applyScalars(
|
||||
'Notfication',
|
||||
'Statistics',
|
||||
'LoggedInUser',
|
||||
'SocialMedia',
|
||||
],
|
||||
// add 'User' here as soon as possible
|
||||
},
|
||||
@ -30,6 +31,7 @@ export default applyScalars(
|
||||
'Notfication',
|
||||
'Statistics',
|
||||
'LoggedInUser',
|
||||
'SocialMedia',
|
||||
],
|
||||
// add 'User' here as soon as possible
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import scrape from './embeds/scraper.js'
|
||||
import { undefinedToNull } from '../helpers'
|
||||
import { undefinedToNullResolver } from './helpers/Resolver'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
@ -8,7 +8,7 @@ export default {
|
||||
},
|
||||
},
|
||||
Embed: {
|
||||
...undefinedToNull([
|
||||
...undefinedToNullResolver([
|
||||
'type',
|
||||
'title',
|
||||
'author',
|
||||
|
||||
75
backend/src/schema/resolvers/helpers/Resolver.js
Normal file
75
backend/src/schema/resolvers/helpers/Resolver.js
Normal file
@ -0,0 +1,75 @@
|
||||
import { neode } from '../../../bootstrap/neo4j'
|
||||
|
||||
export const undefinedToNullResolver = list => {
|
||||
const resolvers = {}
|
||||
list.forEach(key => {
|
||||
resolvers[key] = async (parent, params, context, resolveInfo) => {
|
||||
return typeof parent[key] === 'undefined' ? null : parent[key]
|
||||
}
|
||||
})
|
||||
return resolvers
|
||||
}
|
||||
|
||||
export default function Resolver(type, options = {}) {
|
||||
const instance = neode()
|
||||
const {
|
||||
idAttribute = 'id',
|
||||
undefinedToNull = [],
|
||||
count = {},
|
||||
hasOne = {},
|
||||
hasMany = {},
|
||||
} = options
|
||||
const _hasResolver = (resolvers, { key, connection }, { returnType }) => {
|
||||
return async (parent, params, context, resolveInfo) => {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const id = parent[idAttribute]
|
||||
const statement = `MATCH(:${type} {${idAttribute}: {id}})${connection} RETURN related`
|
||||
const result = await instance.cypher(statement, { id })
|
||||
let response = result.records.map(r => r.get('related').properties)
|
||||
if (returnType === 'object') response = response[0] || null
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
const countResolver = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, connection] of Object.entries(obj)) {
|
||||
resolvers[key] = async (parent, params, context, resolveInfo) => {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const id = parent[idAttribute]
|
||||
const statement = `
|
||||
MATCH(u:${type} {${idAttribute}: {id}})${connection}
|
||||
WHERE NOT related.deleted = true AND NOT related.disabled = true
|
||||
RETURN COUNT(DISTINCT(related)) as count
|
||||
`
|
||||
const result = await instance.cypher(statement, { id })
|
||||
const [response] = result.records.map(r => r.get('count').toNumber())
|
||||
return response
|
||||
}
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
const hasManyResolver = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, connection] of Object.entries(obj)) {
|
||||
resolvers[key] = _hasResolver(resolvers, { key, connection }, { returnType: 'iterable' })
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
const hasOneResolver = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, connection] of Object.entries(obj)) {
|
||||
resolvers[key] = _hasResolver(resolvers, { key, connection }, { returnType: 'object' })
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
const result = {
|
||||
...undefinedToNullResolver(undefinedToNull),
|
||||
...countResolver(count),
|
||||
...hasOneResolver(hasOne),
|
||||
...hasManyResolver(hasMany),
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -1,43 +1,38 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const instance = neode()
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateSocialMedia: async (object, params, context, resolveInfo) => {
|
||||
const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
const session = context.driver.session()
|
||||
await session.run(
|
||||
`MATCH (owner:User {id: $userId}), (socialMedia:SocialMedia {id: $socialMediaId})
|
||||
MERGE (socialMedia)<-[:OWNED]-(owner)
|
||||
RETURN owner`,
|
||||
{
|
||||
userId: context.user.id,
|
||||
socialMediaId: socialMedia.id,
|
||||
},
|
||||
)
|
||||
session.close()
|
||||
const [user, socialMedia] = await Promise.all([
|
||||
instance.find('User', context.user.id),
|
||||
instance.create('SocialMedia', params),
|
||||
])
|
||||
await socialMedia.relateTo(user, 'ownedBy')
|
||||
const response = await socialMedia.toJson()
|
||||
|
||||
return socialMedia
|
||||
},
|
||||
DeleteSocialMedia: async (object, params, context, resolveInfo) => {
|
||||
const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
|
||||
return socialMedia
|
||||
return response
|
||||
},
|
||||
UpdateSocialMedia: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
await session.run(
|
||||
`MATCH (owner: User { id: $userId })-[:OWNED]->(socialMedia: SocialMedia { id: $socialMediaId })
|
||||
SET socialMedia.url = $socialMediaUrl
|
||||
RETURN owner`,
|
||||
{
|
||||
userId: context.user.id,
|
||||
socialMediaId: params.id,
|
||||
socialMediaUrl: params.url,
|
||||
},
|
||||
)
|
||||
session.close()
|
||||
const socialMedia = await instance.find('SocialMedia', params.id)
|
||||
await socialMedia.update({ url: params.url })
|
||||
const response = await socialMedia.toJson()
|
||||
|
||||
return params
|
||||
return response
|
||||
},
|
||||
DeleteSocialMedia: async (object, { id }, context, resolveInfo) => {
|
||||
const socialMedia = await instance.find('SocialMedia', id)
|
||||
if (!socialMedia) return null
|
||||
await socialMedia.delete()
|
||||
return socialMedia.toJson()
|
||||
},
|
||||
},
|
||||
SocialMedia: Resolver('SocialMedia', {
|
||||
idAttribute: 'url',
|
||||
hasOne: {
|
||||
ownedBy: '<-[:OWNED_BY]-(related:User)',
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
@ -1,56 +1,59 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import Factory from '../../seed/factories'
|
||||
import { host, login, gql } from '../../jest/helpers'
|
||||
import { gql } from '../../jest/helpers'
|
||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
const instance = neode()
|
||||
|
||||
describe('SocialMedia', () => {
|
||||
let client, headers, variables, mutation
|
||||
let socialMediaAction, someUser, ownerNode, owner
|
||||
|
||||
const ownerParams = {
|
||||
email: 'owner@example.com',
|
||||
email: 'pippi@example.com',
|
||||
password: '1234',
|
||||
id: '1234',
|
||||
name: 'Pippi Langstrumpf',
|
||||
}
|
||||
|
||||
const userParams = {
|
||||
email: 'someuser@example.com',
|
||||
email: 'kalle@example.com',
|
||||
password: 'abcd',
|
||||
id: 'abcd',
|
||||
name: 'Kalle Blomqvist',
|
||||
}
|
||||
|
||||
const url = 'https://twitter.com/pippi-langstrumpf'
|
||||
const newUrl = 'https://twitter.com/bullerby'
|
||||
|
||||
const createSocialMediaMutation = gql`
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
const updateSocialMediaMutation = gql`
|
||||
mutation($id: ID!, $url: String!) {
|
||||
UpdateSocialMedia(id: $id, url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
const deleteSocialMediaMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
DeleteSocialMedia(id: $id) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
const setUpSocialMedia = async () => {
|
||||
const socialMediaNode = await instance.create('SocialMedia', { url })
|
||||
await socialMediaNode.relateTo(ownerNode, 'ownedBy')
|
||||
return socialMediaNode.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', userParams)
|
||||
await factory.create('User', ownerParams)
|
||||
const someUserNode = await instance.create('User', userParams)
|
||||
someUser = await someUserNode.toJson()
|
||||
ownerNode = await instance.create('User', ownerParams)
|
||||
owner = await ownerNode.toJson()
|
||||
|
||||
socialMediaAction = async (user, mutation, variables) => {
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
})
|
||||
const { mutate } = createTestClient(server)
|
||||
|
||||
return mutate({
|
||||
mutation,
|
||||
variables,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -58,129 +61,213 @@ describe('SocialMedia', () => {
|
||||
})
|
||||
|
||||
describe('create social media', () => {
|
||||
let mutation, variables
|
||||
|
||||
beforeEach(() => {
|
||||
mutation = gql`
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
variables = { url }
|
||||
mutation = createSocialMediaMutation
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
const user = null
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
headers = await login(userParams)
|
||||
client = new GraphQLClient(host, { headers })
|
||||
let user
|
||||
|
||||
beforeEach(() => {
|
||||
user = owner
|
||||
})
|
||||
|
||||
it('creates social media with correct URL', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(
|
||||
it('creates social media with the given url', async () => {
|
||||
await expect(socialMediaAction(user, mutation, variables)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
CreateSocialMedia: {
|
||||
id: expect.any(String),
|
||||
url: url,
|
||||
data: {
|
||||
CreateSocialMedia: {
|
||||
id: expect.any(String),
|
||||
url,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects empty string', async () => {
|
||||
it('rejects an empty string as url', async () => {
|
||||
variables = { url: '' }
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow(
|
||||
'"url" is not allowed to be empty',
|
||||
expect(result.errors[0].message).toEqual(
|
||||
expect.stringContaining('"url" is not allowed to be empty'),
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects invalid URLs', async () => {
|
||||
it('rejects invalid urls', async () => {
|
||||
variables = { url: 'not-a-url' }
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
await expect(client.request(createSocialMediaMutation, variables)).rejects.toThrow(
|
||||
'"url" must be a valid uri',
|
||||
expect(result.errors[0].message).toEqual(
|
||||
expect.stringContaining('"url" must be a valid uri'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ownedBy', () => {
|
||||
beforeEach(() => {
|
||||
mutation = gql`
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
url
|
||||
ownedBy {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
it('resolves', async () => {
|
||||
const user = someUser
|
||||
await expect(socialMediaAction(user, mutation, variables)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
CreateSocialMedia: { url, ownedBy: { name: 'Kalle Blomqvist' } },
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('update social media', () => {
|
||||
let mutation, variables
|
||||
|
||||
beforeEach(async () => {
|
||||
headers = await login(ownerParams)
|
||||
client = new GraphQLClient(host, { headers })
|
||||
const socialMedia = await setUpSocialMedia()
|
||||
|
||||
const { CreateSocialMedia } = await client.request(createSocialMediaMutation, { url })
|
||||
const { id } = CreateSocialMedia
|
||||
|
||||
variables = { url: newUrl, id }
|
||||
mutation = updateSocialMediaMutation
|
||||
mutation = gql`
|
||||
mutation($id: ID!, $url: String!) {
|
||||
UpdateSocialMedia(id: $id, url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
variables = { url: newUrl, id: socialMedia.id }
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
const user = null
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as other user', () => {
|
||||
// TODO: make sure it throws an authorization error
|
||||
it('throws authorization error', async () => {
|
||||
const user = someUser
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as owner', () => {
|
||||
it('updates social media', async () => {
|
||||
const expected = { UpdateSocialMedia: { ...variables } }
|
||||
let user
|
||||
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(
|
||||
beforeEach(() => {
|
||||
user = owner
|
||||
})
|
||||
|
||||
it('updates social media with the given id', async () => {
|
||||
const expected = {
|
||||
data: {
|
||||
UpdateSocialMedia: { ...variables },
|
||||
},
|
||||
}
|
||||
|
||||
await expect(socialMediaAction(user, mutation, variables)).resolves.toEqual(
|
||||
expect.objectContaining(expected),
|
||||
)
|
||||
})
|
||||
|
||||
describe('given a non-existent id', () => {
|
||||
// TODO: make sure it throws an error
|
||||
it('does not update if the the given id does not exist', async () => {
|
||||
variables.id = 'some-id'
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete social media', () => {
|
||||
let mutation, variables
|
||||
|
||||
beforeEach(async () => {
|
||||
headers = await login(ownerParams)
|
||||
client = new GraphQLClient(host, { headers })
|
||||
const socialMedia = await setUpSocialMedia()
|
||||
|
||||
const { CreateSocialMedia } = await client.request(createSocialMediaMutation, { url })
|
||||
const { id } = CreateSocialMedia
|
||||
|
||||
variables = { id }
|
||||
mutation = deleteSocialMediaMutation
|
||||
mutation = gql`
|
||||
mutation($id: ID!) {
|
||||
DeleteSocialMedia(id: $id) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
variables = { url: newUrl, id: socialMedia.id }
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
const user = null
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as other user', () => {
|
||||
// TODO: make sure it throws an authorization error
|
||||
it('throws authorization error', async () => {
|
||||
const user = someUser
|
||||
const result = await socialMediaAction(user, mutation, variables)
|
||||
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as owner', () => {
|
||||
let user
|
||||
|
||||
beforeEach(async () => {
|
||||
headers = await login(ownerParams)
|
||||
client = new GraphQLClient(host, { headers })
|
||||
user = owner
|
||||
})
|
||||
|
||||
it('deletes social media', async () => {
|
||||
it('deletes social media with the given id', async () => {
|
||||
const expected = {
|
||||
DeleteSocialMedia: {
|
||||
id: variables.id,
|
||||
url: url,
|
||||
data: {
|
||||
DeleteSocialMedia: {
|
||||
id: variables.id,
|
||||
url,
|
||||
},
|
||||
},
|
||||
}
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
|
||||
await expect(socialMediaAction(user, mutation, variables)).resolves.toEqual(
|
||||
expect.objectContaining(expected),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,57 +2,10 @@ import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import fileUpload from './fileUpload'
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import { undefinedToNull } from '../helpers'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const instance = neode()
|
||||
|
||||
const _has = (resolvers, { key, connection }, { returnType }) => {
|
||||
return async (parent, params, context, resolveInfo) => {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const { id } = parent
|
||||
const statement = `MATCH(u:User {id: {id}})${connection} RETURN related`
|
||||
const result = await instance.cypher(statement, { id })
|
||||
let response = result.records.map(r => r.get('related').properties)
|
||||
if (returnType === 'object') response = response[0] || null
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
const count = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, connection] of Object.entries(obj)) {
|
||||
resolvers[key] = async (parent, params, context, resolveInfo) => {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const { id } = parent
|
||||
const statement = `
|
||||
MATCH(u:User {id: {id}})${connection}
|
||||
WHERE NOT related.deleted = true AND NOT related.disabled = true
|
||||
RETURN COUNT(DISTINCT(related)) as count
|
||||
`
|
||||
const result = await instance.cypher(statement, { id })
|
||||
const [response] = result.records.map(r => r.get('count').toNumber())
|
||||
return response
|
||||
}
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
export const hasMany = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, connection] of Object.entries(obj)) {
|
||||
resolvers[key] = _has(resolvers, { key, connection }, { returnType: 'iterable' })
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
export const hasOne = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, connection] of Object.entries(obj)) {
|
||||
resolvers[key] = _has(resolvers, { key, connection }, { returnType: 'object' })
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
User: async (object, args, context, resolveInfo) => {
|
||||
@ -110,42 +63,44 @@ export default {
|
||||
let [{ email }] = result.records.map(r => r.get('e').properties)
|
||||
return email
|
||||
},
|
||||
...undefinedToNull([
|
||||
'actorId',
|
||||
'avatar',
|
||||
'coverImg',
|
||||
'deleted',
|
||||
'disabled',
|
||||
'locationName',
|
||||
'about',
|
||||
]),
|
||||
...count({
|
||||
contributionsCount: '-[:WROTE]->(related:Post)',
|
||||
friendsCount: '<-[:FRIENDS]->(related:User)',
|
||||
followingCount: '-[:FOLLOWS]->(related:User)',
|
||||
followedByCount: '<-[:FOLLOWS]-(related:User)',
|
||||
commentsCount: '-[:WROTE]->(r:Comment)',
|
||||
commentedCount: '-[:WROTE]->(:Comment)-[:COMMENTS]->(related:Post)',
|
||||
shoutedCount: '-[:SHOUTED]->(related:Post)',
|
||||
badgesCount: '<-[:REWARDED]-(related:Badge)',
|
||||
}),
|
||||
...hasOne({
|
||||
invitedBy: '<-[:INVITED]-(related:User)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
}),
|
||||
...hasMany({
|
||||
followedBy: '<-[:FOLLOWS]-(related:User)',
|
||||
following: '-[:FOLLOWS]->(related:User)',
|
||||
friends: '-[:FRIENDS]-(related:User)',
|
||||
blacklisted: '-[:BLACKLISTED]->(related:User)',
|
||||
socialMedia: '-[:OWNED]->(related:SocialMedia)',
|
||||
contributions: '-[:WROTE]->(related:Post)',
|
||||
comments: '-[:WROTE]->(related:Comment)',
|
||||
shouted: '-[:SHOUTED]->(related:Post)',
|
||||
organizationsCreated: '-[:CREATED_ORGA]->(related:Organization)',
|
||||
organizationsOwned: '-[:OWNING_ORGA]->(related:Organization)',
|
||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||
badges: '<-[:REWARDED]-(related:Badge)',
|
||||
...Resolver('User', {
|
||||
undefinedToNull: [
|
||||
'actorId',
|
||||
'avatar',
|
||||
'coverImg',
|
||||
'deleted',
|
||||
'disabled',
|
||||
'locationName',
|
||||
'about',
|
||||
],
|
||||
count: {
|
||||
contributionsCount: '-[:WROTE]->(related:Post)',
|
||||
friendsCount: '<-[:FRIENDS]->(related:User)',
|
||||
followingCount: '-[:FOLLOWS]->(related:User)',
|
||||
followedByCount: '<-[:FOLLOWS]-(related:User)',
|
||||
commentsCount: '-[:WROTE]->(r:Comment)',
|
||||
commentedCount: '-[:WROTE]->(:Comment)-[:COMMENTS]->(related:Post)',
|
||||
shoutedCount: '-[:SHOUTED]->(related:Post)',
|
||||
badgesCount: '<-[:REWARDED]-(related:Badge)',
|
||||
},
|
||||
hasOne: {
|
||||
invitedBy: '<-[:INVITED]-(related:User)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
},
|
||||
hasMany: {
|
||||
followedBy: '<-[:FOLLOWS]-(related:User)',
|
||||
following: '-[:FOLLOWS]->(related:User)',
|
||||
friends: '-[:FRIENDS]-(related:User)',
|
||||
blacklisted: '-[:BLACKLISTED]->(related:User)',
|
||||
socialMedia: '-[:OWNED_BY]->(related:SocialMedia',
|
||||
contributions: '-[:WROTE]->(related:Post)',
|
||||
comments: '-[:WROTE]->(related:Comment)',
|
||||
shouted: '-[:SHOUTED]->(related:Post)',
|
||||
organizationsCreated: '-[:CREATED_ORGA]->(related:Organization)',
|
||||
organizationsOwned: '-[:OWNING_ORGA]->(related:Organization)',
|
||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||
badges: '<-[:REWARDED]-(related:Badge)',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@ -131,8 +131,3 @@ type SharedInboxEndpoint {
|
||||
uri: String
|
||||
}
|
||||
|
||||
type SocialMedia {
|
||||
id: ID!
|
||||
url: String
|
||||
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
|
||||
}
|
||||
|
||||
11
backend/src/schema/types/type/SocialMedia.gql
Normal file
11
backend/src/schema/types/type/SocialMedia.gql
Normal file
@ -0,0 +1,11 @@
|
||||
type SocialMedia {
|
||||
id: ID!
|
||||
url: String
|
||||
ownedBy: User! @relation(name: "OWNED_BY", direction: "IN")
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
CreateSocialMedia(id: ID, url: String!): SocialMedia
|
||||
UpdateSocialMedia(id: ID!, url: String!): SocialMedia
|
||||
DeleteSocialMedia(id: ID!): SocialMedia
|
||||
}
|
||||
@ -17,7 +17,7 @@ type User {
|
||||
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
locationName: String
|
||||
about: String
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED", direction: "OUT")
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "OUT")
|
||||
|
||||
#createdAt: DateTime
|
||||
#updatedAt: DateTime
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user