mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Refactor all badges resolvers to use neode
FYI: @Tirokk I think we'll never remove or add new badges through graphql. Instead, we will seed them manually with direct access to the database. Therefore I removed the respective mutations and also your tests regarding permissions.
This commit is contained in:
parent
8c18b9c59b
commit
95a06a8344
@ -161,9 +161,6 @@ const permissions = shield(
|
|||||||
UpdatePost: isAuthor,
|
UpdatePost: isAuthor,
|
||||||
DeletePost: isAuthor,
|
DeletePost: isAuthor,
|
||||||
report: isAuthenticated,
|
report: isAuthenticated,
|
||||||
CreateBadge: isAdmin,
|
|
||||||
UpdateBadge: isAdmin,
|
|
||||||
DeleteBadge: isAdmin,
|
|
||||||
CreateSocialMedia: isAuthenticated,
|
CreateSocialMedia: isAuthenticated,
|
||||||
DeleteSocialMedia: isAuthenticated,
|
DeleteSocialMedia: isAuthenticated,
|
||||||
// AddBadgeRewarded: isAdmin,
|
// AddBadgeRewarded: isAdmin,
|
||||||
|
|||||||
7
backend/src/models/Badge.js
Normal file
7
backend/src/models/Badge.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
key: { type: 'string', primary: true, lowercase: true },
|
||||||
|
status: { type: 'string', valid: ['permanent', 'temporary'] },
|
||||||
|
type: { type: 'string', valid: ['role', 'crowdfunding'] },
|
||||||
|
icon: { type: 'string', required: true },
|
||||||
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
|
}
|
||||||
@ -43,6 +43,12 @@ module.exports = {
|
|||||||
target: 'User',
|
target: 'User',
|
||||||
direction: 'in',
|
direction: 'in',
|
||||||
},
|
},
|
||||||
|
rewarded: {
|
||||||
|
type: 'relationship',
|
||||||
|
relationship: 'REWARDED',
|
||||||
|
target: 'Badge',
|
||||||
|
direction: 'in',
|
||||||
|
},
|
||||||
invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' },
|
invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' },
|
||||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
|
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
|
||||||
// module that is not browser-compatible. Node's `fs` module is server-side only
|
// module that is not browser-compatible. Node's `fs` module is server-side only
|
||||||
export default {
|
export default {
|
||||||
|
Badge: require('./Badge.js'),
|
||||||
User: require('./User.js'),
|
User: require('./User.js'),
|
||||||
InvitationCode: require('./InvitationCode.js'),
|
InvitationCode: require('./InvitationCode.js'),
|
||||||
EmailAddress: require('./EmailAddress.js'),
|
EmailAddress: require('./EmailAddress.js'),
|
||||||
|
|||||||
@ -12,11 +12,25 @@ export default applyScalars(
|
|||||||
resolvers,
|
resolvers,
|
||||||
config: {
|
config: {
|
||||||
query: {
|
query: {
|
||||||
exclude: ['InvitationCode', 'EmailAddress', 'Notfication', 'Statistics', 'LoggedInUser'],
|
exclude: [
|
||||||
|
'Badge',
|
||||||
|
'InvitationCode',
|
||||||
|
'EmailAddress',
|
||||||
|
'Notfication',
|
||||||
|
'Statistics',
|
||||||
|
'LoggedInUser',
|
||||||
|
],
|
||||||
// add 'User' here as soon as possible
|
// add 'User' here as soon as possible
|
||||||
},
|
},
|
||||||
mutation: {
|
mutation: {
|
||||||
exclude: ['InvitationCode', 'EmailAddress', 'Notfication', 'Statistics', 'LoggedInUser'],
|
exclude: [
|
||||||
|
'Badge',
|
||||||
|
'InvitationCode',
|
||||||
|
'EmailAddress',
|
||||||
|
'Notfication',
|
||||||
|
'Statistics',
|
||||||
|
'LoggedInUser',
|
||||||
|
],
|
||||||
// add 'User' here as soon as possible
|
// add 'User' here as soon as possible
|
||||||
},
|
},
|
||||||
debug: CONFIG.DEBUG,
|
debug: CONFIG.DEBUG,
|
||||||
|
|||||||
9
backend/src/schema/resolvers/badges.js
Normal file
9
backend/src/schema/resolvers/badges.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Query: {
|
||||||
|
Badge: async (object, args, context, resolveInfo) => {
|
||||||
|
return neo4jgraphql(object, args, context, resolveInfo, false)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,200 +0,0 @@
|
|||||||
import { GraphQLClient } from 'graphql-request'
|
|
||||||
import Factory from '../../seed/factories'
|
|
||||||
import { host, login } from '../../jest/helpers'
|
|
||||||
|
|
||||||
const factory = Factory()
|
|
||||||
let client
|
|
||||||
|
|
||||||
describe('badges', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await factory.create('User', {
|
|
||||||
email: 'user@example.org',
|
|
||||||
role: 'user',
|
|
||||||
password: '1234',
|
|
||||||
})
|
|
||||||
await factory.create('User', {
|
|
||||||
id: 'u2',
|
|
||||||
role: 'moderator',
|
|
||||||
email: 'moderator@example.org',
|
|
||||||
})
|
|
||||||
await factory.create('User', {
|
|
||||||
id: 'u3',
|
|
||||||
role: 'admin',
|
|
||||||
email: 'admin@example.org',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await factory.cleanDatabase()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('CreateBadge', () => {
|
|
||||||
const variables = {
|
|
||||||
id: 'b1',
|
|
||||||
key: 'indiegogo_en_racoon',
|
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_racoon.svg',
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutation = `
|
|
||||||
mutation(
|
|
||||||
$id: ID
|
|
||||||
$key: String!
|
|
||||||
$type: BadgeType!
|
|
||||||
$status: BadgeStatus!
|
|
||||||
$icon: String!
|
|
||||||
) {
|
|
||||||
CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) {
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
type,
|
|
||||||
status,
|
|
||||||
icon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
|
||||||
it('throws authorization error', async () => {
|
|
||||||
client = new GraphQLClient(host)
|
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated admin', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const headers = await login({ email: 'admin@example.org', password: '1234' })
|
|
||||||
client = new GraphQLClient(host, { headers })
|
|
||||||
})
|
|
||||||
it('creates a badge', async () => {
|
|
||||||
const expected = {
|
|
||||||
CreateBadge: {
|
|
||||||
icon: '/img/badges/indiegogo_en_racoon.svg',
|
|
||||||
id: 'b1',
|
|
||||||
key: 'indiegogo_en_racoon',
|
|
||||||
status: 'permanent',
|
|
||||||
type: 'crowdfunding',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated moderator', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
|
||||||
client = new GraphQLClient(host, { headers })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws authorization error', async () => {
|
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('UpdateBadge', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await factory.authenticateAs({ email: 'admin@example.org', password: '1234' })
|
|
||||||
await factory.create('Badge', { id: 'b1' })
|
|
||||||
})
|
|
||||||
const variables = {
|
|
||||||
id: 'b1',
|
|
||||||
key: 'whatever',
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutation = `
|
|
||||||
mutation($id: ID!, $key: String!) {
|
|
||||||
UpdateBadge(id: $id, key: $key) {
|
|
||||||
id
|
|
||||||
key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
|
||||||
it('throws authorization error', async () => {
|
|
||||||
client = new GraphQLClient(host)
|
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated moderator', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
|
||||||
client = new GraphQLClient(host, { headers })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws authorization error', async () => {
|
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated admin', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const headers = await login({ email: 'admin@example.org', password: '1234' })
|
|
||||||
client = new GraphQLClient(host, { headers })
|
|
||||||
})
|
|
||||||
it('updates a badge', async () => {
|
|
||||||
const expected = {
|
|
||||||
UpdateBadge: {
|
|
||||||
id: 'b1',
|
|
||||||
key: 'whatever',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('DeleteBadge', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await factory.authenticateAs({ email: 'admin@example.org', password: '1234' })
|
|
||||||
await factory.create('Badge', { id: 'b1' })
|
|
||||||
})
|
|
||||||
const variables = {
|
|
||||||
id: 'b1',
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutation = `
|
|
||||||
mutation($id: ID!) {
|
|
||||||
DeleteBadge(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
|
||||||
it('throws authorization error', async () => {
|
|
||||||
client = new GraphQLClient(host)
|
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated moderator', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
|
||||||
client = new GraphQLClient(host, { headers })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws authorization error', async () => {
|
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated admin', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const headers = await login({ email: 'admin@example.org', password: '1234' })
|
|
||||||
client = new GraphQLClient(host, { headers })
|
|
||||||
})
|
|
||||||
it('deletes a badge', async () => {
|
|
||||||
const expected = {
|
|
||||||
DeleteBadge: {
|
|
||||||
id: 'b1',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,47 +1,47 @@
|
|||||||
|
import { neode } from '../../bootstrap/neo4j'
|
||||||
|
import { UserInputError } from 'apollo-server'
|
||||||
|
|
||||||
|
const instance = neode()
|
||||||
|
|
||||||
|
const getUserAndBadge = async ({ badgeKey, userId }) => {
|
||||||
|
let user = await instance.first('User', 'id', userId)
|
||||||
|
const badge = await instance.first('Badge', 'key', badgeKey)
|
||||||
|
if (!user) throw new UserInputError("Couldn't find a user with that id")
|
||||||
|
if (!badge) throw new UserInputError("Couldn't find a badge with that key")
|
||||||
|
return { user, badge }
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
reward: async (_object, params, context, _resolveInfo) => {
|
reward: async (_object, params, context, _resolveInfo) => {
|
||||||
const { fromBadgeId, toUserId } = params
|
const { user, badge } = await getUserAndBadge(params)
|
||||||
const session = context.driver.session()
|
await user.relateTo(badge, 'rewarded')
|
||||||
|
return user.toJson()
|
||||||
let transactionRes = await session.run(
|
|
||||||
`MATCH (badge:Badge {id: $badgeId}), (rewardedUser:User {id: $rewardedUserId})
|
|
||||||
MERGE (badge)-[:REWARDED]->(rewardedUser)
|
|
||||||
RETURN rewardedUser {.id}`,
|
|
||||||
{
|
|
||||||
badgeId: fromBadgeId,
|
|
||||||
rewardedUserId: toUserId,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const [rewardedUser] = transactionRes.records.map(record => {
|
|
||||||
return record.get('rewardedUser')
|
|
||||||
})
|
|
||||||
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
return rewardedUser.id
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unreward: async (_object, params, context, _resolveInfo) => {
|
unreward: async (_object, params, context, _resolveInfo) => {
|
||||||
const { fromBadgeId, toUserId } = params
|
const { badgeKey, userId } = params
|
||||||
|
const { user } = await getUserAndBadge(params)
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
|
try {
|
||||||
let transactionRes = await session.run(
|
// silly neode cannot remove relationships
|
||||||
`MATCH (badge:Badge {id: $badgeId})-[reward:REWARDED]->(rewardedUser:User {id: $rewardedUserId})
|
await session.run(
|
||||||
DELETE reward
|
`
|
||||||
RETURN rewardedUser {.id}`,
|
MATCH (badge:Badge {key: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
|
||||||
{
|
DELETE reward
|
||||||
badgeId: fromBadgeId,
|
RETURN rewardedUser
|
||||||
rewardedUserId: toUserId,
|
`,
|
||||||
},
|
{
|
||||||
)
|
badgeKey,
|
||||||
const [rewardedUser] = transactionRes.records.map(record => {
|
userId,
|
||||||
return record.get('rewardedUser')
|
},
|
||||||
})
|
)
|
||||||
session.close()
|
} catch (err) {
|
||||||
|
throw err
|
||||||
return rewardedUser.id
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
return user.toJson()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,20 @@
|
|||||||
import { GraphQLClient } from 'graphql-request'
|
import { GraphQLClient } from 'graphql-request'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { host, login } from '../../jest/helpers'
|
import { host, login } from '../../jest/helpers'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
|
let user
|
||||||
|
let badge
|
||||||
|
|
||||||
describe('rewards', () => {
|
describe('rewards', () => {
|
||||||
|
const variables = {
|
||||||
|
from: 'indiegogo_en_rhino',
|
||||||
|
to: 'u1',
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.create('User', {
|
user = await factory.create('User', {
|
||||||
id: 'u1',
|
id: 'u1',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
@ -22,8 +30,7 @@ describe('rewards', () => {
|
|||||||
role: 'admin',
|
role: 'admin',
|
||||||
email: 'admin@example.org',
|
email: 'admin@example.org',
|
||||||
})
|
})
|
||||||
await factory.create('Badge', {
|
badge = await factory.create('Badge', {
|
||||||
id: 'b6',
|
|
||||||
key: 'indiegogo_en_rhino',
|
key: 'indiegogo_en_rhino',
|
||||||
type: 'crowdfunding',
|
type: 'crowdfunding',
|
||||||
status: 'permanent',
|
status: 'permanent',
|
||||||
@ -35,21 +42,19 @@ describe('rewards', () => {
|
|||||||
await factory.cleanDatabase()
|
await factory.cleanDatabase()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('RewardBadge', () => {
|
describe('reward', () => {
|
||||||
const mutation = `
|
const mutation = gql`
|
||||||
mutation(
|
mutation($from: ID!, $to: ID!) {
|
||||||
$from: ID!
|
reward(badgeKey: $from, userId: $to) {
|
||||||
$to: ID!
|
id
|
||||||
) {
|
badges {
|
||||||
reward(fromBadgeId: $from, toUserId: $to)
|
key
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
const variables = {
|
|
||||||
from: 'b6',
|
|
||||||
to: 'u1',
|
|
||||||
}
|
|
||||||
let client
|
let client
|
||||||
|
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
@ -65,74 +70,94 @@ describe('rewards', () => {
|
|||||||
client = new GraphQLClient(host, { headers })
|
client = new GraphQLClient(host, { headers })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('badge for key does not exist', () => {
|
||||||
|
it('rejects with a telling error message', async () => {
|
||||||
|
await expect(
|
||||||
|
client.request(mutation, {
|
||||||
|
...variables,
|
||||||
|
from: 'bullshit',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("Couldn't find a badge with that key")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user for id does not exist', () => {
|
||||||
|
it('rejects with a telling error message', async () => {
|
||||||
|
await expect(
|
||||||
|
client.request(mutation, {
|
||||||
|
...variables,
|
||||||
|
to: 'bullshit',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("Couldn't find a user with that id")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('rewards a badge to user', async () => {
|
it('rewards a badge to user', async () => {
|
||||||
const variables = {
|
|
||||||
from: 'b6',
|
|
||||||
to: 'u1',
|
|
||||||
}
|
|
||||||
const expected = {
|
const expected = {
|
||||||
reward: 'u1',
|
reward: {
|
||||||
|
id: 'u1',
|
||||||
|
badges: [{ key: 'indiegogo_en_rhino' }],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rewards a second different badge to same user', async () => {
|
it('rewards a second different badge to same user', async () => {
|
||||||
await factory.create('Badge', {
|
await factory.create('Badge', {
|
||||||
id: 'b1',
|
|
||||||
key: 'indiegogo_en_racoon',
|
key: 'indiegogo_en_racoon',
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_racoon.svg',
|
icon: '/img/badges/indiegogo_en_racoon.svg',
|
||||||
})
|
})
|
||||||
const variables = {
|
|
||||||
from: 'b1',
|
|
||||||
to: 'u1',
|
|
||||||
}
|
|
||||||
const expected = {
|
const expected = {
|
||||||
reward: 'u1',
|
reward: {
|
||||||
|
id: 'u1',
|
||||||
|
badges: [{ key: 'indiegogo_en_racoon' }, { key: 'indiegogo_en_rhino' }],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
await client.request(mutation, variables)
|
||||||
|
await expect(
|
||||||
|
client.request(mutation, {
|
||||||
|
...variables,
|
||||||
|
from: 'indiegogo_en_racoon',
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rewards the same badge as well to another user', async () => {
|
it('rewards the same badge as well to another user', async () => {
|
||||||
const variables1 = {
|
|
||||||
from: 'b6',
|
|
||||||
to: 'u1',
|
|
||||||
}
|
|
||||||
await client.request(mutation, variables1)
|
|
||||||
|
|
||||||
const variables2 = {
|
|
||||||
from: 'b6',
|
|
||||||
to: 'u2',
|
|
||||||
}
|
|
||||||
const expected = {
|
const expected = {
|
||||||
reward: 'u2',
|
reward: {
|
||||||
|
id: 'u2',
|
||||||
|
badges: [{ key: 'indiegogo_en_rhino' }],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
await expect(client.request(mutation, variables2)).resolves.toEqual(expected)
|
await expect(
|
||||||
|
client.request(mutation, {
|
||||||
|
...variables,
|
||||||
|
to: 'u2',
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
it('returns the original reward if a reward is attempted a second time', async () => {
|
|
||||||
const variables = {
|
it('creates no duplicate reward relationships', async () => {
|
||||||
from: 'b6',
|
|
||||||
to: 'u1',
|
|
||||||
}
|
|
||||||
await client.request(mutation, variables)
|
await client.request(mutation, variables)
|
||||||
await client.request(mutation, variables)
|
await client.request(mutation, variables)
|
||||||
|
|
||||||
const query = `{
|
const query = gql`
|
||||||
User( id: "u1" ) {
|
{
|
||||||
badgesCount
|
User(id: "u1") {
|
||||||
|
badgesCount
|
||||||
|
badges {
|
||||||
|
key
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`
|
`
|
||||||
const expected = { User: [{ badgesCount: 1 }] }
|
const expected = { User: [{ badgesCount: 1, badges: [{ key: 'indiegogo_en_rhino' }] }] }
|
||||||
|
|
||||||
await expect(client.request(query)).resolves.toEqual(expected)
|
await expect(client.request(query)).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('authenticated moderator', () => {
|
describe('authenticated moderator', () => {
|
||||||
const variables = {
|
|
||||||
from: 'b6',
|
|
||||||
to: 'u1',
|
|
||||||
}
|
|
||||||
let client
|
let client
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
||||||
@ -147,27 +172,41 @@ describe('rewards', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('RemoveReward', () => {
|
describe('unreward', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.relate('User', 'Badges', { from: 'b6', to: 'u1' })
|
await user.relateTo(badge, 'rewarded')
|
||||||
})
|
})
|
||||||
const variables = {
|
const expected = { unreward: { id: 'u1', badges: [] } }
|
||||||
from: 'b6',
|
|
||||||
to: 'u1',
|
|
||||||
}
|
|
||||||
const expected = {
|
|
||||||
unreward: 'u1',
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutation = `
|
const mutation = gql`
|
||||||
mutation(
|
mutation($from: ID!, $to: ID!) {
|
||||||
$from: ID!
|
unreward(badgeKey: $from, userId: $to) {
|
||||||
$to: ID!
|
id
|
||||||
) {
|
badges {
|
||||||
unreward(fromBadgeId: $from, toUserId: $to)
|
key
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
describe('check test setup', () => {
|
||||||
|
it('user has one badge', async () => {
|
||||||
|
const query = gql`
|
||||||
|
{
|
||||||
|
User(id: "u1") {
|
||||||
|
badgesCount
|
||||||
|
badges {
|
||||||
|
key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expected = { User: [{ badgesCount: 1, badges: [{ key: 'indiegogo_en_rhino' }] }] }
|
||||||
|
const client = new GraphQLClient(host)
|
||||||
|
await expect(client.request(query)).resolves.toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
let client
|
let client
|
||||||
|
|
||||||
@ -188,12 +227,9 @@ describe('rewards', () => {
|
|||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails to remove a not existing badge from user', async () => {
|
it('does not crash when unrewarding multiple times', async () => {
|
||||||
await client.request(mutation, variables)
|
await client.request(mutation, variables)
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow(
|
|
||||||
"Cannot read property 'id' of undefined",
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -139,7 +139,7 @@ export default {
|
|||||||
organizationsCreated: '-[:CREATED_ORGA]->(related:Organization)',
|
organizationsCreated: '-[:CREATED_ORGA]->(related:Organization)',
|
||||||
organizationsOwned: '-[:OWNING_ORGA]->(related:Organization)',
|
organizationsOwned: '-[:OWNING_ORGA]->(related:Organization)',
|
||||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||||
badges: '-[:REWARDED]->(related:Badge)',
|
badges: '<-[:REWARDED]-(related:Badge)',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -147,7 +147,7 @@ describe('users', () => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
asAuthor = await factory.create('User', {
|
await factory.create('User', {
|
||||||
email: 'test@example.org',
|
email: 'test@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
id: 'u343',
|
id: 'u343',
|
||||||
@ -191,6 +191,7 @@ describe('users', () => {
|
|||||||
describe('attempting to delete my own account', () => {
|
describe('attempting to delete my own account', () => {
|
||||||
let expectedResponse
|
let expectedResponse
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
asAuthor = Factory()
|
||||||
await asAuthor.authenticateAs({
|
await asAuthor.authenticateAs({
|
||||||
email: 'test@example.org',
|
email: 'test@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
enum BadgeStatus {
|
|
||||||
permanent
|
|
||||||
temporary
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
enum BadgeType {
|
|
||||||
role
|
|
||||||
crowdfunding
|
|
||||||
}
|
|
||||||
@ -28,8 +28,6 @@ type Mutation {
|
|||||||
report(id: ID!, description: String): Report
|
report(id: ID!, description: String): Report
|
||||||
disable(id: ID!): ID
|
disable(id: ID!): ID
|
||||||
enable(id: ID!): ID
|
enable(id: ID!): ID
|
||||||
reward(fromBadgeId: ID!, toUserId: ID!): ID
|
|
||||||
unreward(fromBadgeId: ID!, toUserId: ID!): ID
|
|
||||||
# Shout the given Type and ID
|
# Shout the given Type and ID
|
||||||
shout(id: ID!, type: ShoutTypeEnum): Boolean!
|
shout(id: ID!, type: ShoutTypeEnum): Boolean!
|
||||||
# Unshout the given Type and ID
|
# Unshout the given Type and ID
|
||||||
|
|||||||
@ -29,8 +29,8 @@ type Mutation {
|
|||||||
report(id: ID!, description: String): Report
|
report(id: ID!, description: String): Report
|
||||||
disable(id: ID!): ID
|
disable(id: ID!): ID
|
||||||
enable(id: ID!): ID
|
enable(id: ID!): ID
|
||||||
reward(fromBadgeId: ID!, toUserId: ID!): ID
|
reward(fromBadgeKey: ID!, toUserId: ID!): ID
|
||||||
unreward(fromBadgeId: ID!, toUserId: ID!): ID
|
unreward(fromBadgeKey: ID!, toUserId: ID!): ID
|
||||||
# Shout the given Type and ID
|
# Shout the given Type and ID
|
||||||
shout(id: ID!, type: ShoutTypeEnum): Boolean!
|
shout(id: ID!, type: ShoutTypeEnum): Boolean!
|
||||||
# Unshout the given Type and ID
|
# Unshout the given Type and ID
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
type Badge {
|
type Badge {
|
||||||
id: ID!
|
key: ID!
|
||||||
key: String!
|
|
||||||
type: BadgeType!
|
type: BadgeType!
|
||||||
status: BadgeStatus!
|
status: BadgeStatus!
|
||||||
icon: String!
|
icon: String!
|
||||||
@ -10,4 +9,23 @@ type Badge {
|
|||||||
updatedAt: String
|
updatedAt: String
|
||||||
|
|
||||||
rewarded: [User]! @relation(name: "REWARDED", direction: "OUT")
|
rewarded: [User]! @relation(name: "REWARDED", direction: "OUT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BadgeStatus {
|
||||||
|
permanent
|
||||||
|
temporary
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BadgeType {
|
||||||
|
role
|
||||||
|
crowdfunding
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
Badge: [Badge]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
reward(badgeKey: ID!, userId: ID!): User
|
||||||
|
unreward(badgeKey: ID!, userId: ID!): User
|
||||||
|
}
|
||||||
|
|||||||
@ -1,28 +1,15 @@
|
|||||||
import uuid from 'uuid/v4'
|
export default function create() {
|
||||||
|
|
||||||
export default function(params) {
|
|
||||||
const {
|
|
||||||
id = uuid(),
|
|
||||||
key = '',
|
|
||||||
type = 'crowdfunding',
|
|
||||||
status = 'permanent',
|
|
||||||
icon = '/img/badges/indiegogo_en_panda.svg',
|
|
||||||
} = params
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mutation: `
|
factory: async ({ args, neodeInstance }) => {
|
||||||
mutation(
|
const defaults = {
|
||||||
$id: ID
|
type: 'crowdfunding',
|
||||||
$key: String!
|
status: 'permanent',
|
||||||
$type: BadgeType!
|
|
||||||
$status: BadgeStatus!
|
|
||||||
$icon: String!
|
|
||||||
) {
|
|
||||||
CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`,
|
args = {
|
||||||
variables: { id, key, type, status, icon },
|
...defaults,
|
||||||
|
...args,
|
||||||
|
}
|
||||||
|
return neodeInstance.create('Badge', args)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,6 +73,7 @@ export default function Factory(options = {}) {
|
|||||||
const { factory, mutation, variables } = this.factories[node](args)
|
const { factory, mutation, variables } = this.factories[node](args)
|
||||||
if (factory) {
|
if (factory) {
|
||||||
this.lastResponse = await factory({ args, neodeInstance })
|
this.lastResponse = await factory({ args, neodeInstance })
|
||||||
|
return this.lastResponse
|
||||||
} else {
|
} else {
|
||||||
this.lastResponse = await this.graphQLClient.request(mutation, variables)
|
this.lastResponse = await this.graphQLClient.request(mutation, variables)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import uuid from 'uuid/v4'
|
|||||||
import encryptPassword from '../../helpers/encryptPassword'
|
import encryptPassword from '../../helpers/encryptPassword'
|
||||||
import slugify from 'slug'
|
import slugify from 'slug'
|
||||||
|
|
||||||
export default function create(params) {
|
export default function create() {
|
||||||
return {
|
return {
|
||||||
factory: async ({ args, neodeInstance }) => {
|
factory: async ({ args, neodeInstance }) => {
|
||||||
const defaults = {
|
const defaults = {
|
||||||
@ -21,8 +21,7 @@ export default function create(params) {
|
|||||||
...args,
|
...args,
|
||||||
}
|
}
|
||||||
args = await encryptPassword(args)
|
args = await encryptPassword(args)
|
||||||
const user = await neodeInstance.create('User', args)
|
return neodeInstance.create('User', args)
|
||||||
return user.toJson()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,52 +5,42 @@ import Factory from './factories'
|
|||||||
;(async function() {
|
;(async function() {
|
||||||
try {
|
try {
|
||||||
const f = Factory()
|
const f = Factory()
|
||||||
await Promise.all([
|
const [racoon, rabbit, wolf, bear, turtle, rhino] = await Promise.all([
|
||||||
f.create('Badge', {
|
f.create('Badge', {
|
||||||
id: 'b1',
|
|
||||||
key: 'indiegogo_en_racoon',
|
key: 'indiegogo_en_racoon',
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_racoon.svg',
|
icon: '/img/badges/indiegogo_en_racoon.svg',
|
||||||
}),
|
}),
|
||||||
f.create('Badge', {
|
f.create('Badge', {
|
||||||
id: 'b2',
|
|
||||||
key: 'indiegogo_en_rabbit',
|
key: 'indiegogo_en_rabbit',
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_rabbit.svg',
|
icon: '/img/badges/indiegogo_en_rabbit.svg',
|
||||||
}),
|
}),
|
||||||
f.create('Badge', {
|
f.create('Badge', {
|
||||||
id: 'b3',
|
|
||||||
key: 'indiegogo_en_wolf',
|
key: 'indiegogo_en_wolf',
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_wolf.svg',
|
icon: '/img/badges/indiegogo_en_wolf.svg',
|
||||||
}),
|
}),
|
||||||
f.create('Badge', {
|
f.create('Badge', {
|
||||||
id: 'b4',
|
|
||||||
key: 'indiegogo_en_bear',
|
key: 'indiegogo_en_bear',
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_bear.svg',
|
icon: '/img/badges/indiegogo_en_bear.svg',
|
||||||
}),
|
}),
|
||||||
f.create('Badge', {
|
f.create('Badge', {
|
||||||
id: 'b5',
|
|
||||||
key: 'indiegogo_en_turtle',
|
key: 'indiegogo_en_turtle',
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_turtle.svg',
|
icon: '/img/badges/indiegogo_en_turtle.svg',
|
||||||
}),
|
}),
|
||||||
f.create('Badge', {
|
f.create('Badge', {
|
||||||
id: 'b6',
|
|
||||||
key: 'indiegogo_en_rhino',
|
key: 'indiegogo_en_rhino',
|
||||||
type: 'crowdfunding',
|
|
||||||
status: 'permanent',
|
|
||||||
icon: '/img/badges/indiegogo_en_rhino.svg',
|
icon: '/img/badges/indiegogo_en_rhino.svg',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
await Promise.all([
|
const [
|
||||||
|
peterLustig,
|
||||||
|
bobDerBaumeister,
|
||||||
|
jennyRostock,
|
||||||
|
tick, // eslint-disable-line no-unused-vars
|
||||||
|
trick, // eslint-disable-line no-unused-vars
|
||||||
|
track, // eslint-disable-line no-unused-vars
|
||||||
|
dagobert,
|
||||||
|
] = await Promise.all([
|
||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u1',
|
id: 'u1',
|
||||||
name: 'Peter Lustig',
|
name: 'Peter Lustig',
|
||||||
@ -123,30 +113,16 @@ import Factory from './factories'
|
|||||||
])
|
])
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
f.relate('User', 'Badges', {
|
peterLustig.relateTo(racoon, 'rewarded'),
|
||||||
from: 'b6',
|
peterLustig.relateTo(rhino, 'rewarded'),
|
||||||
to: 'u1',
|
peterLustig.relateTo(wolf, 'rewarded'),
|
||||||
}),
|
bobDerBaumeister.relateTo(racoon, 'rewarded'),
|
||||||
f.relate('User', 'Badges', {
|
bobDerBaumeister.relateTo(turtle, 'rewarded'),
|
||||||
from: 'b5',
|
jennyRostock.relateTo(bear, 'rewarded'),
|
||||||
to: 'u2',
|
dagobert.relateTo(rabbit, 'rewarded'),
|
||||||
}),
|
])
|
||||||
f.relate('User', 'Badges', {
|
|
||||||
from: 'b4',
|
await Promise.all([
|
||||||
to: 'u3',
|
|
||||||
}),
|
|
||||||
f.relate('User', 'Badges', {
|
|
||||||
from: 'b3',
|
|
||||||
to: 'u4',
|
|
||||||
}),
|
|
||||||
f.relate('User', 'Badges', {
|
|
||||||
from: 'b2',
|
|
||||||
to: 'u5',
|
|
||||||
}),
|
|
||||||
f.relate('User', 'Badges', {
|
|
||||||
from: 'b1',
|
|
||||||
to: 'u6',
|
|
||||||
}),
|
|
||||||
f.relate('User', 'Friends', {
|
f.relate('User', 'Friends', {
|
||||||
from: 'u1',
|
from: 'u1',
|
||||||
to: 'u2',
|
to: 'u2',
|
||||||
|
|||||||
@ -24,7 +24,6 @@ export default app => {
|
|||||||
name: name${lang}
|
name: name${lang}
|
||||||
}
|
}
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,6 @@ export default i18n => {
|
|||||||
name: name${lang}
|
name: name${lang}
|
||||||
}
|
}
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,6 @@ export default i18n => {
|
|||||||
name: name${lang}
|
name: name${lang}
|
||||||
}
|
}
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
@ -60,7 +59,6 @@ export default i18n => {
|
|||||||
name: name${lang}
|
name: name${lang}
|
||||||
}
|
}
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,6 @@ export default i18n => {
|
|||||||
}
|
}
|
||||||
createdAt
|
createdAt
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
@ -38,7 +37,6 @@ export default i18n => {
|
|||||||
contributionsCount
|
contributionsCount
|
||||||
commentsCount
|
commentsCount
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
@ -60,7 +58,6 @@ export default i18n => {
|
|||||||
contributionsCount
|
contributionsCount
|
||||||
commentsCount
|
commentsCount
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,7 +156,6 @@ export default {
|
|||||||
name: name${this.$i18n.locale().toUpperCase()}
|
name: name${this.$i18n.locale().toUpperCase()}
|
||||||
}
|
}
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,6 @@ export default {
|
|||||||
name: name${this.$i18n.locale().toUpperCase()}
|
name: name${this.$i18n.locale().toUpperCase()}
|
||||||
}
|
}
|
||||||
badges {
|
badges {
|
||||||
id
|
|
||||||
key
|
key
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user