From 8c18b9c59bf67f1cf3a282786eee02aa429d39c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 10 Jul 2019 12:36:19 +0200 Subject: [PATCH 1/7] Allow to list all badges --- backend/src/middleware/permissionsMiddleware.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index a6b6ef0da..d38f7fd08 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -146,6 +146,7 @@ const permissions = shield( Comment: allow, User: or(noEmailFilter, isAdmin), isLoggedIn: allow, + Badge: allow, }, Mutation: { '*': deny, From 95a06a8344c6f0d2ca8e1e9c3abe4c90158f1ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 10 Jul 2019 13:59:02 +0200 Subject: [PATCH 2/7] 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. --- .../src/middleware/permissionsMiddleware.js | 3 - backend/src/models/Badge.js | 7 + backend/src/models/User.js | 6 + backend/src/models/index.js | 1 + backend/src/schema/index.js | 18 +- backend/src/schema/resolvers/badges.js | 9 + backend/src/schema/resolvers/badges.spec.js | 200 ------------------ backend/src/schema/resolvers/rewards.js | 74 +++---- backend/src/schema/resolvers/rewards.spec.js | 184 +++++++++------- backend/src/schema/resolvers/users.js | 2 +- backend/src/schema/resolvers/users.spec.js | 3 +- backend/src/schema/types/enum/BadgeStatus.gql | 4 - backend/src/schema/types/enum/BadgeType.gql | 4 - backend/src/schema/types/schema.gql | 2 - backend/src/schema/types/schema_full.gql_ | 4 +- backend/src/schema/types/type/Badge.gql | 24 ++- backend/src/seed/factories/badges.js | 35 +-- backend/src/seed/factories/index.js | 1 + backend/src/seed/factories/users.js | 5 +- backend/src/seed/seed-db.js | 64 ++---- webapp/graphql/CommentQuery.js | 1 - webapp/graphql/PostCommentsQuery.js | 1 - webapp/graphql/PostQuery.js | 2 - webapp/graphql/UserProfile/User.js | 3 - webapp/pages/index.vue | 1 - webapp/pages/post/_id/_slug/more-info.vue | 1 - 26 files changed, 246 insertions(+), 413 deletions(-) create mode 100644 backend/src/models/Badge.js create mode 100644 backend/src/schema/resolvers/badges.js delete mode 100644 backend/src/schema/resolvers/badges.spec.js delete mode 100644 backend/src/schema/types/enum/BadgeStatus.gql delete mode 100644 backend/src/schema/types/enum/BadgeType.gql diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index d38f7fd08..31c373fc7 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -161,9 +161,6 @@ const permissions = shield( UpdatePost: isAuthor, DeletePost: isAuthor, report: isAuthenticated, - CreateBadge: isAdmin, - UpdateBadge: isAdmin, - DeleteBadge: isAdmin, CreateSocialMedia: isAuthenticated, DeleteSocialMedia: isAuthenticated, // AddBadgeRewarded: isAdmin, diff --git a/backend/src/models/Badge.js b/backend/src/models/Badge.js new file mode 100644 index 000000000..211d7a25b --- /dev/null +++ b/backend/src/models/Badge.js @@ -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() }, +} diff --git a/backend/src/models/User.js b/backend/src/models/User.js index d8f768ae9..02ce04513 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -43,6 +43,12 @@ module.exports = { target: 'User', direction: 'in', }, + rewarded: { + type: 'relationship', + relationship: 'REWARDED', + target: 'Badge', + direction: 'in', + }, invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, updatedAt: { diff --git a/backend/src/models/index.js b/backend/src/models/index.js index 0e6ae5864..09d1dbbeb 100644 --- a/backend/src/models/index.js +++ b/backend/src/models/index.js @@ -1,6 +1,7 @@ // 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 export default { + Badge: require('./Badge.js'), User: require('./User.js'), InvitationCode: require('./InvitationCode.js'), EmailAddress: require('./EmailAddress.js'), diff --git a/backend/src/schema/index.js b/backend/src/schema/index.js index 8fbb5cfda..0f724d9b5 100644 --- a/backend/src/schema/index.js +++ b/backend/src/schema/index.js @@ -12,11 +12,25 @@ export default applyScalars( resolvers, config: { query: { - exclude: ['InvitationCode', 'EmailAddress', 'Notfication', 'Statistics', 'LoggedInUser'], + exclude: [ + 'Badge', + 'InvitationCode', + 'EmailAddress', + 'Notfication', + 'Statistics', + 'LoggedInUser', + ], // add 'User' here as soon as possible }, mutation: { - exclude: ['InvitationCode', 'EmailAddress', 'Notfication', 'Statistics', 'LoggedInUser'], + exclude: [ + 'Badge', + 'InvitationCode', + 'EmailAddress', + 'Notfication', + 'Statistics', + 'LoggedInUser', + ], // add 'User' here as soon as possible }, debug: CONFIG.DEBUG, diff --git a/backend/src/schema/resolvers/badges.js b/backend/src/schema/resolvers/badges.js new file mode 100644 index 000000000..19bc24fd6 --- /dev/null +++ b/backend/src/schema/resolvers/badges.js @@ -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) + }, + }, +} diff --git a/backend/src/schema/resolvers/badges.spec.js b/backend/src/schema/resolvers/badges.spec.js deleted file mode 100644 index a0dbafe00..000000000 --- a/backend/src/schema/resolvers/badges.spec.js +++ /dev/null @@ -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) - }) - }) - }) -}) diff --git a/backend/src/schema/resolvers/rewards.js b/backend/src/schema/resolvers/rewards.js index ec5043da3..cfad513ff 100644 --- a/backend/src/schema/resolvers/rewards.js +++ b/backend/src/schema/resolvers/rewards.js @@ -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 { Mutation: { reward: async (_object, params, context, _resolveInfo) => { - const { fromBadgeId, toUserId } = params - const session = context.driver.session() - - 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 + const { user, badge } = await getUserAndBadge(params) + await user.relateTo(badge, 'rewarded') + return user.toJson() }, unreward: async (_object, params, context, _resolveInfo) => { - const { fromBadgeId, toUserId } = params + const { badgeKey, userId } = params + const { user } = await getUserAndBadge(params) const session = context.driver.session() - - let transactionRes = await session.run( - `MATCH (badge:Badge {id: $badgeId})-[reward:REWARDED]->(rewardedUser:User {id: $rewardedUserId}) - DELETE reward - RETURN rewardedUser {.id}`, - { - badgeId: fromBadgeId, - rewardedUserId: toUserId, - }, - ) - const [rewardedUser] = transactionRes.records.map(record => { - return record.get('rewardedUser') - }) - session.close() - - return rewardedUser.id + try { + // silly neode cannot remove relationships + await session.run( + ` + MATCH (badge:Badge {key: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId}) + DELETE reward + RETURN rewardedUser + `, + { + badgeKey, + userId, + }, + ) + } catch (err) { + throw err + } finally { + session.close() + } + return user.toJson() }, }, } diff --git a/backend/src/schema/resolvers/rewards.spec.js b/backend/src/schema/resolvers/rewards.spec.js index 2bdd9a39b..d835e2255 100644 --- a/backend/src/schema/resolvers/rewards.spec.js +++ b/backend/src/schema/resolvers/rewards.spec.js @@ -1,12 +1,20 @@ import { GraphQLClient } from 'graphql-request' import Factory from '../../seed/factories' import { host, login } from '../../jest/helpers' +import gql from 'graphql-tag' const factory = Factory() +let user +let badge describe('rewards', () => { + const variables = { + from: 'indiegogo_en_rhino', + to: 'u1', + } + beforeEach(async () => { - await factory.create('User', { + user = await factory.create('User', { id: 'u1', role: 'user', email: 'user@example.org', @@ -22,8 +30,7 @@ describe('rewards', () => { role: 'admin', email: 'admin@example.org', }) - await factory.create('Badge', { - id: 'b6', + badge = await factory.create('Badge', { key: 'indiegogo_en_rhino', type: 'crowdfunding', status: 'permanent', @@ -35,21 +42,19 @@ describe('rewards', () => { await factory.cleanDatabase() }) - describe('RewardBadge', () => { - const mutation = ` - mutation( - $from: ID! - $to: ID! - ) { - reward(fromBadgeId: $from, toUserId: $to) + describe('reward', () => { + const mutation = gql` + mutation($from: ID!, $to: ID!) { + reward(badgeKey: $from, userId: $to) { + id + badges { + key + } + } } ` describe('unauthenticated', () => { - const variables = { - from: 'b6', - to: 'u1', - } let client it('throws authorization error', async () => { @@ -65,74 +70,94 @@ describe('rewards', () => { 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 () => { - const variables = { - from: 'b6', - to: 'u1', - } const expected = { - reward: 'u1', + reward: { + id: 'u1', + badges: [{ key: 'indiegogo_en_rhino' }], + }, } await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) + it('rewards a second different badge to same user', async () => { await factory.create('Badge', { - id: 'b1', key: 'indiegogo_en_racoon', - type: 'crowdfunding', - status: 'permanent', icon: '/img/badges/indiegogo_en_racoon.svg', }) - const variables = { - from: 'b1', - to: 'u1', - } 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 () => { - const variables1 = { - from: 'b6', - to: 'u1', - } - await client.request(mutation, variables1) - - const variables2 = { - from: 'b6', - to: 'u2', - } 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 = { - from: 'b6', - to: 'u1', - } + + it('creates no duplicate reward relationships', async () => { await client.request(mutation, variables) await client.request(mutation, variables) - const query = `{ - User( id: "u1" ) { - badgesCount + const query = gql` + { + 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) }) }) describe('authenticated moderator', () => { - const variables = { - from: 'b6', - to: 'u1', - } let client beforeEach(async () => { const headers = await login({ email: 'moderator@example.org', password: '1234' }) @@ -147,27 +172,41 @@ describe('rewards', () => { }) }) - describe('RemoveReward', () => { + describe('unreward', () => { beforeEach(async () => { - await factory.relate('User', 'Badges', { from: 'b6', to: 'u1' }) + await user.relateTo(badge, 'rewarded') }) - const variables = { - from: 'b6', - to: 'u1', - } - const expected = { - unreward: 'u1', - } + const expected = { unreward: { id: 'u1', badges: [] } } - const mutation = ` - mutation( - $from: ID! - $to: ID! - ) { - unreward(fromBadgeId: $from, toUserId: $to) + const mutation = gql` + mutation($from: ID!, $to: ID!) { + unreward(badgeKey: $from, userId: $to) { + id + badges { + 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', () => { let client @@ -188,12 +227,9 @@ describe('rewards', () => { 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 expect(client.request(mutation, variables)).rejects.toThrow( - "Cannot read property 'id' of undefined", - ) + await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) }) diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 2d9282b60..ea076d005 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -139,7 +139,7 @@ export default { organizationsCreated: '-[:CREATED_ORGA]->(related:Organization)', organizationsOwned: '-[:OWNING_ORGA]->(related:Organization)', categories: '-[:CATEGORIZED]->(related:Category)', - badges: '-[:REWARDED]->(related:Badge)', + badges: '<-[:REWARDED]-(related:Badge)', }), }, } diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 6f9b6dd3d..fa83017da 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -147,7 +147,7 @@ describe('users', () => { } ` beforeEach(async () => { - asAuthor = await factory.create('User', { + await factory.create('User', { email: 'test@example.org', password: '1234', id: 'u343', @@ -191,6 +191,7 @@ describe('users', () => { describe('attempting to delete my own account', () => { let expectedResponse beforeEach(async () => { + asAuthor = Factory() await asAuthor.authenticateAs({ email: 'test@example.org', password: '1234', diff --git a/backend/src/schema/types/enum/BadgeStatus.gql b/backend/src/schema/types/enum/BadgeStatus.gql deleted file mode 100644 index b109663b3..000000000 --- a/backend/src/schema/types/enum/BadgeStatus.gql +++ /dev/null @@ -1,4 +0,0 @@ -enum BadgeStatus { - permanent - temporary -} \ No newline at end of file diff --git a/backend/src/schema/types/enum/BadgeType.gql b/backend/src/schema/types/enum/BadgeType.gql deleted file mode 100644 index eccf2e661..000000000 --- a/backend/src/schema/types/enum/BadgeType.gql +++ /dev/null @@ -1,4 +0,0 @@ -enum BadgeType { - role - crowdfunding -} \ No newline at end of file diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 261501600..492dd3966 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -28,8 +28,6 @@ type Mutation { report(id: ID!, description: String): Report disable(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(id: ID!, type: ShoutTypeEnum): Boolean! # Unshout the given Type and ID diff --git a/backend/src/schema/types/schema_full.gql_ b/backend/src/schema/types/schema_full.gql_ index a581d287c..3454c8100 100644 --- a/backend/src/schema/types/schema_full.gql_ +++ b/backend/src/schema/types/schema_full.gql_ @@ -29,8 +29,8 @@ type Mutation { report(id: ID!, description: String): Report disable(id: ID!): ID enable(id: ID!): ID - reward(fromBadgeId: ID!, toUserId: ID!): ID - unreward(fromBadgeId: ID!, toUserId: ID!): ID + reward(fromBadgeKey: ID!, toUserId: ID!): ID + unreward(fromBadgeKey: ID!, toUserId: ID!): ID # Shout the given Type and ID shout(id: ID!, type: ShoutTypeEnum): Boolean! # Unshout the given Type and ID diff --git a/backend/src/schema/types/type/Badge.gql b/backend/src/schema/types/type/Badge.gql index 68c5d5707..aac2960a0 100644 --- a/backend/src/schema/types/type/Badge.gql +++ b/backend/src/schema/types/type/Badge.gql @@ -1,6 +1,5 @@ type Badge { - id: ID! - key: String! + key: ID! type: BadgeType! status: BadgeStatus! icon: String! @@ -10,4 +9,23 @@ type Badge { updatedAt: String rewarded: [User]! @relation(name: "REWARDED", direction: "OUT") -} \ No newline at end of file +} + +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 +} diff --git a/backend/src/seed/factories/badges.js b/backend/src/seed/factories/badges.js index 6414e9f36..5f0482460 100644 --- a/backend/src/seed/factories/badges.js +++ b/backend/src/seed/factories/badges.js @@ -1,28 +1,15 @@ -import uuid from 'uuid/v4' - -export default function(params) { - const { - id = uuid(), - key = '', - type = 'crowdfunding', - status = 'permanent', - icon = '/img/badges/indiegogo_en_panda.svg', - } = params - +export default function create() { return { - mutation: ` - mutation( - $id: ID - $key: String! - $type: BadgeType! - $status: BadgeStatus! - $icon: String! - ) { - CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) { - id - } + factory: async ({ args, neodeInstance }) => { + const defaults = { + type: 'crowdfunding', + status: 'permanent', } - `, - variables: { id, key, type, status, icon }, + args = { + ...defaults, + ...args, + } + return neodeInstance.create('Badge', args) + }, } } diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js index b2cf2de45..e841b0beb 100644 --- a/backend/src/seed/factories/index.js +++ b/backend/src/seed/factories/index.js @@ -73,6 +73,7 @@ export default function Factory(options = {}) { const { factory, mutation, variables } = this.factories[node](args) if (factory) { this.lastResponse = await factory({ args, neodeInstance }) + return this.lastResponse } else { this.lastResponse = await this.graphQLClient.request(mutation, variables) } diff --git a/backend/src/seed/factories/users.js b/backend/src/seed/factories/users.js index ffe8e7a39..8bdf03b9f 100644 --- a/backend/src/seed/factories/users.js +++ b/backend/src/seed/factories/users.js @@ -3,7 +3,7 @@ import uuid from 'uuid/v4' import encryptPassword from '../../helpers/encryptPassword' import slugify from 'slug' -export default function create(params) { +export default function create() { return { factory: async ({ args, neodeInstance }) => { const defaults = { @@ -21,8 +21,7 @@ export default function create(params) { ...args, } args = await encryptPassword(args) - const user = await neodeInstance.create('User', args) - return user.toJson() + return neodeInstance.create('User', args) }, } } diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index 18eefb76f..14258ec85 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -5,52 +5,42 @@ import Factory from './factories' ;(async function() { try { const f = Factory() - await Promise.all([ + const [racoon, rabbit, wolf, bear, turtle, rhino] = await Promise.all([ f.create('Badge', { - id: 'b1', key: 'indiegogo_en_racoon', - type: 'crowdfunding', - status: 'permanent', icon: '/img/badges/indiegogo_en_racoon.svg', }), f.create('Badge', { - id: 'b2', key: 'indiegogo_en_rabbit', - type: 'crowdfunding', - status: 'permanent', icon: '/img/badges/indiegogo_en_rabbit.svg', }), f.create('Badge', { - id: 'b3', key: 'indiegogo_en_wolf', - type: 'crowdfunding', - status: 'permanent', icon: '/img/badges/indiegogo_en_wolf.svg', }), f.create('Badge', { - id: 'b4', key: 'indiegogo_en_bear', - type: 'crowdfunding', - status: 'permanent', icon: '/img/badges/indiegogo_en_bear.svg', }), f.create('Badge', { - id: 'b5', key: 'indiegogo_en_turtle', - type: 'crowdfunding', - status: 'permanent', icon: '/img/badges/indiegogo_en_turtle.svg', }), f.create('Badge', { - id: 'b6', key: 'indiegogo_en_rhino', - type: 'crowdfunding', - status: 'permanent', 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', { id: 'u1', name: 'Peter Lustig', @@ -123,30 +113,16 @@ import Factory from './factories' ]) await Promise.all([ - f.relate('User', 'Badges', { - from: 'b6', - to: 'u1', - }), - f.relate('User', 'Badges', { - from: 'b5', - to: 'u2', - }), - f.relate('User', 'Badges', { - from: 'b4', - 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', - }), + peterLustig.relateTo(racoon, 'rewarded'), + peterLustig.relateTo(rhino, 'rewarded'), + peterLustig.relateTo(wolf, 'rewarded'), + bobDerBaumeister.relateTo(racoon, 'rewarded'), + bobDerBaumeister.relateTo(turtle, 'rewarded'), + jennyRostock.relateTo(bear, 'rewarded'), + dagobert.relateTo(rabbit, 'rewarded'), + ]) + + await Promise.all([ f.relate('User', 'Friends', { from: 'u1', to: 'u2', diff --git a/webapp/graphql/CommentQuery.js b/webapp/graphql/CommentQuery.js index 0e61a8a6c..fef5e74c1 100644 --- a/webapp/graphql/CommentQuery.js +++ b/webapp/graphql/CommentQuery.js @@ -24,7 +24,6 @@ export default app => { name: name${lang} } badges { - id key icon } diff --git a/webapp/graphql/PostCommentsQuery.js b/webapp/graphql/PostCommentsQuery.js index bbdfa1178..e3a5ce858 100644 --- a/webapp/graphql/PostCommentsQuery.js +++ b/webapp/graphql/PostCommentsQuery.js @@ -28,7 +28,6 @@ export default i18n => { name: name${lang} } badges { - id key icon } diff --git a/webapp/graphql/PostQuery.js b/webapp/graphql/PostQuery.js index 240f718ae..31444ae0c 100644 --- a/webapp/graphql/PostQuery.js +++ b/webapp/graphql/PostQuery.js @@ -29,7 +29,6 @@ export default i18n => { name: name${lang} } badges { - id key icon } @@ -60,7 +59,6 @@ export default i18n => { name: name${lang} } badges { - id key icon } diff --git a/webapp/graphql/UserProfile/User.js b/webapp/graphql/UserProfile/User.js index 5bfe0510f..19bf5c6e9 100644 --- a/webapp/graphql/UserProfile/User.js +++ b/webapp/graphql/UserProfile/User.js @@ -18,7 +18,6 @@ export default i18n => { } createdAt badges { - id key icon } @@ -38,7 +37,6 @@ export default i18n => { contributionsCount commentsCount badges { - id key icon } @@ -60,7 +58,6 @@ export default i18n => { contributionsCount commentsCount badges { - id key icon } diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue index 5769f1828..3a51e9f8e 100644 --- a/webapp/pages/index.vue +++ b/webapp/pages/index.vue @@ -156,7 +156,6 @@ export default { name: name${this.$i18n.locale().toUpperCase()} } badges { - id key icon } diff --git a/webapp/pages/post/_id/_slug/more-info.vue b/webapp/pages/post/_id/_slug/more-info.vue index 167926e95..8495c5a5b 100644 --- a/webapp/pages/post/_id/_slug/more-info.vue +++ b/webapp/pages/post/_id/_slug/more-info.vue @@ -110,7 +110,6 @@ export default { name: name${this.$i18n.locale().toUpperCase()} } badges { - id key icon } From fdd8095e4878a8d45f1ffa8781988cfc66d6f885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 10 Jul 2019 14:41:29 +0200 Subject: [PATCH 3/7] Remove always out-of-date schema.gql dump --- backend/src/schema/types/schema_full.gql_ | 324 ---------------------- 1 file changed, 324 deletions(-) delete mode 100644 backend/src/schema/types/schema_full.gql_ diff --git a/backend/src/schema/types/schema_full.gql_ b/backend/src/schema/types/schema_full.gql_ deleted file mode 100644 index 3454c8100..000000000 --- a/backend/src/schema/types/schema_full.gql_ +++ /dev/null @@ -1,324 +0,0 @@ -scalar Upload - -type Query { - isLoggedIn: Boolean! - # Get the currently logged in User based on the given JWT Token - currentUser: User - # Get the latest Network Statistics - statistics: Statistics! - findPosts(filter: String!, limit: Int = 10): [Post]! @cypher( - statement: """ - CALL db.index.fulltext.queryNodes('full_text_search', $filter) - YIELD node as post, score - MATCH (post)<-[:WROTE]-(user:User) - WHERE score >= 0.2 - AND NOT user.deleted = true AND NOT user.disabled = true - AND NOT post.deleted = true AND NOT post.disabled = true - RETURN post - LIMIT $limit - """ - ) - CommentByPost(postId: ID!): [Comment]! -} - -type Mutation { - # Get a JWT Token for the given Email and password - login(email: String!, password: String!): String! - signup(email: String!, password: String!): Boolean! - changePassword(oldPassword:String!, newPassword: String!): String! - report(id: ID!, description: String): Report - disable(id: ID!): ID - enable(id: ID!): ID - reward(fromBadgeKey: ID!, toUserId: ID!): ID - unreward(fromBadgeKey: ID!, toUserId: ID!): ID - # Shout the given Type and ID - shout(id: ID!, type: ShoutTypeEnum): Boolean! - # Unshout the given Type and ID - unshout(id: ID!, type: ShoutTypeEnum): Boolean! - # Follow the given Type and ID - follow(id: ID!, type: FollowTypeEnum): Boolean! - # Unfollow the given Type and ID - unfollow(id: ID!, type: FollowTypeEnum): Boolean! -} - -type Statistics { - countUsers: Int! - countPosts: Int! - countComments: Int! - countNotifications: Int! - countOrganizations: Int! - countProjects: Int! - countInvites: Int! - countFollows: Int! - countShouts: Int! -} - -type Notification { - id: ID! - read: Boolean, - user: User @relation(name: "NOTIFIED", direction: "OUT") - post: Post @relation(name: "NOTIFIED", direction: "IN") - createdAt: String -} - -scalar Date -scalar Time -scalar DateTime - -enum VisibilityEnum { - public - friends - private -} - -enum UserGroupEnum { - admin - moderator - user -} - -type Location { - id: ID! - name: String! - nameEN: String - nameDE: String - nameFR: String - nameNL: String - nameIT: String - nameES: String - namePT: String - namePL: String - type: String! - lat: Float - lng: Float - parent: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") -} - -type User { - id: ID! - actorId: String - name: String - email: String! - slug: String - password: String! - avatar: String - avatarUpload: Upload - deleted: Boolean - disabled: Boolean - disabledBy: User @relation(name: "DISABLED", direction: "IN") - role: UserGroupEnum - publicKey: String - privateKey: String - - location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") - locationName: String - about: String - socialMedia: [SocialMedia]! @relation(name: "OWNED", direction: "OUT") - - createdAt: String - updatedAt: String - - notifications(read: Boolean): [Notification]! @relation(name: "NOTIFIED", direction: "IN") - - friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") - friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)") - - following: [User]! @relation(name: "FOLLOWS", direction: "OUT") - followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(DISTINCT r)") - - followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") - followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)") - - # Is the currently logged in user following that user? - followedByCurrentUser: Boolean! @cypher( - statement: """ - MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) - RETURN COUNT(u) >= 1 - """ - ) - - #contributions: [WrittenPost]! - #contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! - # @cypher( - # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" - # ) - contributions: [Post]! @relation(name: "WROTE", direction: "OUT") - contributionsCount: Int! @cypher( - statement: """ - MATCH (this)-[:WROTE]->(r:Post) - WHERE (NOT exists(r.deleted) OR r.deleted = false) - AND (NOT exists(r.disabled) OR r.disabled = false) - RETURN COUNT(r) - """ - ) - - comments: [Comment]! @relation(name: "WROTE", direction: "OUT") - commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)") - - shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") - shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") - - organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT") - organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT") - - blacklisted: [User]! @relation(name: "BLACKLISTED", direction: "OUT") - - categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") - - badges: [Badge]! @relation(name: "REWARDED", direction: "IN") - badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") -} - -type Post { - id: ID! - activityId: String - objectId: String - author: User @relation(name: "WROTE", direction: "IN") - title: String! - slug: String - content: String! - contentExcerpt: String - image: String - imageUpload: Upload - visibility: VisibilityEnum - deleted: Boolean - disabled: Boolean - disabledBy: User @relation(name: "DISABLED", direction: "IN") - createdAt: String - updatedAt: String - - relatedContributions: [Post]! @cypher( - statement: """ - MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post) - RETURN DISTINCT post - LIMIT 10 - """ - ) - - tags: [Tag]! @relation(name: "TAGGED", direction: "OUT") - categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") - - comments: [Comment]! @relation(name: "COMMENTS", direction: "IN") - commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)") - - shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN") - shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") - - # Has the currently logged in user shouted that post? - shoutedByCurrentUser: Boolean! @cypher( - statement: """ - MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId}) - RETURN COUNT(u) >= 1 - """ - ) -} - -type Comment { - id: ID! - activityId: String - postId: ID - author: User @relation(name: "WROTE", direction: "IN") - content: String! - contentExcerpt: String - post: Post @relation(name: "COMMENTS", direction: "OUT") - createdAt: String - updatedAt: String - deleted: Boolean - disabled: Boolean - disabledBy: User @relation(name: "DISABLED", direction: "IN") -} - -type Report { - id: ID! - submitter: User @relation(name: "REPORTED", direction: "IN") - description: String - type: String! @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]") - createdAt: String - comment: Comment @relation(name: "REPORTED", direction: "OUT") - post: Post @relation(name: "REPORTED", direction: "OUT") - user: User @relation(name: "REPORTED", direction: "OUT") -} - -type Category { - id: ID! - name: String! - slug: String - icon: String! - posts: [Post]! @relation(name: "CATEGORIZED", direction: "IN") - postCount: Int! @cypher(statement: "MATCH (this)<-[:CATEGORIZED]-(r:Post) RETURN COUNT(r)") -} - -type Badge { - id: ID! - key: String! - type: BadgeTypeEnum! - status: BadgeStatusEnum! - icon: String! - - rewarded: [User]! @relation(name: "REWARDED", direction: "OUT") -} - -enum BadgeTypeEnum { - role - crowdfunding -} -enum BadgeStatusEnum { - permanent - temporary -} -enum ShoutTypeEnum { - Post - Organization - Project -} -enum FollowTypeEnum { - User - Organization - Project -} - -type Reward { - id: ID! - user: User @relation(name: "REWARDED", direction: "IN") - rewarderId: ID - createdAt: String - badge: Badge @relation(name: "REWARDED", direction: "OUT") -} - -type Organization { - id: ID! - createdBy: User @relation(name: "CREATED_ORGA", direction: "IN") - ownedBy: [User] @relation(name: "OWNING_ORGA", direction: "IN") - name: String! - slug: String - description: String! - descriptionExcerpt: String - deleted: Boolean - disabled: Boolean - - tags: [Tag]! @relation(name: "TAGGED", direction: "OUT") - categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") -} - -type Tag { - id: ID! - name: String! - taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN") - taggedOrganizations: [Organization]! @relation(name: "TAGGED", direction: "IN") - taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(DISTINCT p)") - taggedCountUnique: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p)<-[:WROTE]-(u:User) RETURN COUNT(DISTINCT u)") - deleted: Boolean - disabled: Boolean -} - -type SharedInboxEndpoint { - id: ID! - uri: String -} - -type SocialMedia { - id: ID! - url: String - ownedBy: [User]! @relation(name: "OWNED", direction: "IN") -} - From 3447cdffeb2c03b561322984369637e4a7987bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 10 Jul 2019 17:56:36 +0200 Subject: [PATCH 4/7] Chainable factories for cypress --- cypress/support/factories.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cypress/support/factories.js b/cypress/support/factories.js index dd16e8198..825f65b83 100644 --- a/cypress/support/factories.js +++ b/cypress/support/factories.js @@ -23,24 +23,27 @@ Cypress.Commands.add('factory', () => { Cypress.Commands.add( 'create', { prevSubject: true }, - (factory, node, properties) => { - return factory.create(node, properties) + async (factory, node, properties) => { + await factory.create(node, properties) + return factory } ) Cypress.Commands.add( 'relate', { prevSubject: true }, - (factory, node, relationship, properties) => { - return factory.relate(node, relationship, properties) + async (factory, node, relationship, properties) => { + await factory.relate(node, relationship, properties) + return factory } ) Cypress.Commands.add( 'mutate', { prevSubject: true }, - (factory, mutation, variables) => { - return factory.mutate(mutation, variables) + async (factory, mutation, variables) => { + await factory.mutate(mutation, variables) + return factory } ) From 9a72e7b9e6e8544cfb865eac24439680e379d510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 10 Jul 2019 18:01:18 +0200 Subject: [PATCH 5/7] Proper null handling FYI @mattwr18 --- webapp/components/comments/CommentList/index.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webapp/components/comments/CommentList/index.vue b/webapp/components/comments/CommentList/index.vue index ab5916629..ee1370bea 100644 --- a/webapp/components/comments/CommentList/index.vue +++ b/webapp/components/comments/CommentList/index.vue @@ -47,7 +47,8 @@ export default { }, watch: { Post(post) { - this.comments = post[0].comments || [] + const [first] = post + this.comments = first && first.comments || [] }, }, apollo: { From b6dcafcc2bb386a7f0fe5c960c33f2cd821b085f Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 10 Jul 2019 18:52:23 -0300 Subject: [PATCH 6/7] Fix linting --- webapp/components/comments/CommentList/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/components/comments/CommentList/index.vue b/webapp/components/comments/CommentList/index.vue index ee1370bea..db9a4974d 100644 --- a/webapp/components/comments/CommentList/index.vue +++ b/webapp/components/comments/CommentList/index.vue @@ -48,7 +48,7 @@ export default { watch: { Post(post) { const [first] = post - this.comments = first && first.comments || [] + this.comments = (first && first.comments) || [] }, }, apollo: { From 99740e6ad61abca07fee2276d71e1907f5ada453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sat, 13 Jul 2019 14:25:07 +0200 Subject: [PATCH 7/7] Follow @Tirokk's suggestion and rename `key`=>`id` --- backend/src/models/Badge.js | 2 +- backend/src/schema/resolvers/rewards.js | 6 ++--- backend/src/schema/resolvers/rewards.spec.js | 26 +++++++++---------- backend/src/schema/types/type/Badge.gql | 2 +- backend/src/seed/seed-db.js | 12 ++++----- .../migration/neo4j/badges/badges.cql | 4 +-- webapp/components/Badges.vue | 2 +- webapp/graphql/CommentQuery.js | 2 +- webapp/graphql/PostCommentsQuery.js | 2 +- webapp/graphql/PostQuery.js | 4 +-- webapp/graphql/UserProfile/User.js | 6 ++--- webapp/pages/index.vue | 2 +- webapp/pages/post/_id/_slug/more-info.vue | 2 +- 13 files changed, 36 insertions(+), 36 deletions(-) diff --git a/backend/src/models/Badge.js b/backend/src/models/Badge.js index 211d7a25b..6968a056b 100644 --- a/backend/src/models/Badge.js +++ b/backend/src/models/Badge.js @@ -1,5 +1,5 @@ module.exports = { - key: { type: 'string', primary: true, lowercase: true }, + id: { type: 'string', primary: true, lowercase: true }, status: { type: 'string', valid: ['permanent', 'temporary'] }, type: { type: 'string', valid: ['role', 'crowdfunding'] }, icon: { type: 'string', required: true }, diff --git a/backend/src/schema/resolvers/rewards.js b/backend/src/schema/resolvers/rewards.js index cfad513ff..f7a759aa4 100644 --- a/backend/src/schema/resolvers/rewards.js +++ b/backend/src/schema/resolvers/rewards.js @@ -5,9 +5,9 @@ const instance = neode() const getUserAndBadge = async ({ badgeKey, userId }) => { let user = await instance.first('User', 'id', userId) - const badge = await instance.first('Badge', 'key', badgeKey) + const badge = await instance.first('Badge', 'id', 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") + if (!badge) throw new UserInputError("Couldn't find a badge with that id") return { user, badge } } @@ -27,7 +27,7 @@ export default { // silly neode cannot remove relationships await session.run( ` - MATCH (badge:Badge {key: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId}) + MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId}) DELETE reward RETURN rewardedUser `, diff --git a/backend/src/schema/resolvers/rewards.spec.js b/backend/src/schema/resolvers/rewards.spec.js index d835e2255..7dcca6eb8 100644 --- a/backend/src/schema/resolvers/rewards.spec.js +++ b/backend/src/schema/resolvers/rewards.spec.js @@ -31,7 +31,7 @@ describe('rewards', () => { email: 'admin@example.org', }) badge = await factory.create('Badge', { - key: 'indiegogo_en_rhino', + id: 'indiegogo_en_rhino', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_rhino.svg', @@ -48,7 +48,7 @@ describe('rewards', () => { reward(badgeKey: $from, userId: $to) { id badges { - key + id } } } @@ -70,14 +70,14 @@ describe('rewards', () => { client = new GraphQLClient(host, { headers }) }) - describe('badge for key does not exist', () => { + describe('badge for id 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") + ).rejects.toThrow("Couldn't find a badge with that id") }) }) @@ -96,7 +96,7 @@ describe('rewards', () => { const expected = { reward: { id: 'u1', - badges: [{ key: 'indiegogo_en_rhino' }], + badges: [{ id: 'indiegogo_en_rhino' }], }, } await expect(client.request(mutation, variables)).resolves.toEqual(expected) @@ -104,13 +104,13 @@ describe('rewards', () => { it('rewards a second different badge to same user', async () => { await factory.create('Badge', { - key: 'indiegogo_en_racoon', + id: 'indiegogo_en_racoon', icon: '/img/badges/indiegogo_en_racoon.svg', }) const expected = { reward: { id: 'u1', - badges: [{ key: 'indiegogo_en_racoon' }, { key: 'indiegogo_en_rhino' }], + badges: [{ id: 'indiegogo_en_racoon' }, { id: 'indiegogo_en_rhino' }], }, } await client.request(mutation, variables) @@ -126,7 +126,7 @@ describe('rewards', () => { const expected = { reward: { id: 'u2', - badges: [{ key: 'indiegogo_en_rhino' }], + badges: [{ id: 'indiegogo_en_rhino' }], }, } await expect( @@ -146,12 +146,12 @@ describe('rewards', () => { User(id: "u1") { badgesCount badges { - key + id } } } ` - const expected = { User: [{ badgesCount: 1, badges: [{ key: 'indiegogo_en_rhino' }] }] } + const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] } await expect(client.request(query)).resolves.toEqual(expected) }) @@ -183,7 +183,7 @@ describe('rewards', () => { unreward(badgeKey: $from, userId: $to) { id badges { - key + id } } } @@ -196,12 +196,12 @@ describe('rewards', () => { User(id: "u1") { badgesCount badges { - key + id } } } ` - const expected = { User: [{ badgesCount: 1, badges: [{ key: 'indiegogo_en_rhino' }] }] } + const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] } const client = new GraphQLClient(host) await expect(client.request(query)).resolves.toEqual(expected) }) diff --git a/backend/src/schema/types/type/Badge.gql b/backend/src/schema/types/type/Badge.gql index aac2960a0..99015a518 100644 --- a/backend/src/schema/types/type/Badge.gql +++ b/backend/src/schema/types/type/Badge.gql @@ -1,5 +1,5 @@ type Badge { - key: ID! + id: ID! type: BadgeType! status: BadgeStatus! icon: String! diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index 14258ec85..188336572 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -7,27 +7,27 @@ import Factory from './factories' const f = Factory() const [racoon, rabbit, wolf, bear, turtle, rhino] = await Promise.all([ f.create('Badge', { - key: 'indiegogo_en_racoon', + id: 'indiegogo_en_racoon', icon: '/img/badges/indiegogo_en_racoon.svg', }), f.create('Badge', { - key: 'indiegogo_en_rabbit', + id: 'indiegogo_en_rabbit', icon: '/img/badges/indiegogo_en_rabbit.svg', }), f.create('Badge', { - key: 'indiegogo_en_wolf', + id: 'indiegogo_en_wolf', icon: '/img/badges/indiegogo_en_wolf.svg', }), f.create('Badge', { - key: 'indiegogo_en_bear', + id: 'indiegogo_en_bear', icon: '/img/badges/indiegogo_en_bear.svg', }), f.create('Badge', { - key: 'indiegogo_en_turtle', + id: 'indiegogo_en_turtle', icon: '/img/badges/indiegogo_en_turtle.svg', }), f.create('Badge', { - key: 'indiegogo_en_rhino', + id: 'indiegogo_en_rhino', icon: '/img/badges/indiegogo_en_rhino.svg', }), ]) diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/badges.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/badges.cql index 027cea019..adf63dc1f 100644 --- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/badges.cql +++ b/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges/badges.cql @@ -25,7 +25,7 @@ [?] type: String, // in nitro this is a defined enum - seems good for now [X] required: true }, -[X] key: { +[X] id: { [X] type: String, [X] required: true }, @@ -43,7 +43,7 @@ CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as badge MERGE(b:Badge {id: badge._id["$oid"]}) ON CREATE SET -b.key = badge.key, +b.id = badge.key, b.type = badge.type, b.icon = replace(badge.image.path, 'https://api-alpha.human-connection.org', ''), b.status = badge.status, diff --git a/webapp/components/Badges.vue b/webapp/components/Badges.vue index 42ac23e4d..936d13adb 100644 --- a/webapp/components/Badges.vue +++ b/webapp/components/Badges.vue @@ -1,6 +1,6 @@