diff --git a/backend/src/activitypub/ActivityPub.js b/backend/src/activitypub/ActivityPub.js index 3ce27e109..da1056362 100644 --- a/backend/src/activitypub/ActivityPub.js +++ b/backend/src/activitypub/ActivityPub.js @@ -1,13 +1,5 @@ -import { - extractNameFromId, - extractDomainFromUrl, - signAndSend -} from './utils' -import { - isPublicAddressed, - sendAcceptActivity, - sendRejectActivity -} from './utils/activity' +import { extractNameFromId, extractDomainFromUrl, signAndSend } from './utils' +import { isPublicAddressed, sendAcceptActivity, sendRejectActivity } from './utils/activity' import request from 'request' import as from 'activitystrea.ms' import NitroDataSource from './NitroDataSource' @@ -22,182 +14,203 @@ let activityPub = null export { activityPub } export default class ActivityPub { - constructor (activityPubEndpointUri, internalGraphQlUri) { + constructor(activityPubEndpointUri, internalGraphQlUri) { this.endpoint = activityPubEndpointUri this.dataSource = new NitroDataSource(internalGraphQlUri) this.collections = new Collections(this.dataSource) } - static init (server) { + static init(server) { if (!activityPub) { dotenv.config() - activityPub = new ActivityPub(process.env.CLIENT_URI || 'http://localhost:3000', process.env.GRAPHQL_URI || 'http://localhost:4000') + activityPub = new ActivityPub( + process.env.CLIENT_URI || 'http://localhost:3000', + process.env.GRAPHQL_URI || 'http://localhost:4000', + ) // integrate into running graphql express server server.express.set('ap', activityPub) server.express.use(router) - console.log('-> ActivityPub middleware added to the graphql express server') + console.log('-> ActivityPub middleware added to the graphql express server') // eslint-disable-line no-console } else { - console.log('-> ActivityPub middleware already added to the graphql express server') + console.log('-> ActivityPub middleware already added to the graphql express server') // eslint-disable-line no-console } } - handleFollowActivity (activity) { + handleFollowActivity(activity) { debug(`inside FOLLOW ${activity.actor}`) let toActorName = extractNameFromId(activity.object) let fromDomain = extractDomainFromUrl(activity.actor) const dataSource = this.dataSource return new Promise((resolve, reject) => { - request({ - url: activity.actor, - headers: { - 'Accept': 'application/activity+json' - } - }, async (err, response, toActorObject) => { - if (err) return reject(err) - // save shared inbox - toActorObject = JSON.parse(toActorObject) - await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox) + request( + { + url: activity.actor, + headers: { + Accept: 'application/activity+json', + }, + }, + async (err, response, toActorObject) => { + if (err) return reject(err) + // save shared inbox + toActorObject = JSON.parse(toActorObject) + await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox) - let followersCollectionPage = await this.dataSource.getFollowersCollectionPage(activity.object) + let followersCollectionPage = await this.dataSource.getFollowersCollectionPage( + activity.object, + ) - const followActivity = as.follow() - .id(activity.id) - .actor(activity.actor) - .object(activity.object) + const followActivity = as + .follow() + .id(activity.id) + .actor(activity.actor) + .object(activity.object) - // add follower if not already in collection - if (followersCollectionPage.orderedItems.includes(activity.actor)) { - debug('follower already in collection!') + // add follower if not already in collection + if (followersCollectionPage.orderedItems.includes(activity.actor)) { + debug('follower already in collection!') + debug(`inbox = ${toActorObject.inbox}`) + resolve( + sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), + ) + } else { + followersCollectionPage.orderedItems.push(activity.actor) + } + debug(`toActorObject = ${toActorObject}`) + toActorObject = + typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject + debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`) debug(`inbox = ${toActorObject.inbox}`) - resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox)) - } else { - followersCollectionPage.orderedItems.push(activity.actor) - } - debug(`toActorObject = ${toActorObject}`) - toActorObject = typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject - debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`) - debug(`inbox = ${toActorObject.inbox}`) - debug(`outbox = ${toActorObject.outbox}`) - debug(`followers = ${toActorObject.followers}`) - debug(`following = ${toActorObject.following}`) + debug(`outbox = ${toActorObject.outbox}`) + debug(`followers = ${toActorObject.followers}`) + debug(`following = ${toActorObject.following}`) - try { - await dataSource.saveFollowersCollectionPage(followersCollectionPage) - debug('follow activity saved') - resolve(sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox)) - } catch (e) { - debug('followers update error!', e) - resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox)) - } - }) + try { + await dataSource.saveFollowersCollectionPage(followersCollectionPage) + debug('follow activity saved') + resolve( + sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), + ) + } catch (e) { + debug('followers update error!', e) + resolve( + sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), + ) + } + }, + ) }) } - handleUndoActivity (activity) { + handleUndoActivity(activity) { debug('inside UNDO') switch (activity.object.type) { - case 'Follow': - const followActivity = activity.object - return this.dataSource.undoFollowActivity(followActivity.actor, followActivity.object) - case 'Like': - return this.dataSource.deleteShouted(activity) - default: + case 'Follow': + const followActivity = activity.object + return this.dataSource.undoFollowActivity(followActivity.actor, followActivity.object) + case 'Like': + return this.dataSource.deleteShouted(activity) + default: } } - handleCreateActivity (activity) { + handleCreateActivity(activity) { debug('inside create') switch (activity.object.type) { - case 'Article': - case 'Note': - const articleObject = activity.object - if (articleObject.inReplyTo) { - return this.dataSource.createComment(activity) - } else { - return this.dataSource.createPost(activity) - } - default: + case 'Article': + case 'Note': + const articleObject = activity.object + if (articleObject.inReplyTo) { + return this.dataSource.createComment(activity) + } else { + return this.dataSource.createPost(activity) + } + default: } } - handleDeleteActivity (activity) { + handleDeleteActivity(activity) { debug('inside delete') switch (activity.object.type) { - case 'Article': - case 'Note': - return this.dataSource.deletePost(activity) - default: + case 'Article': + case 'Note': + return this.dataSource.deletePost(activity) + default: } } - handleUpdateActivity (activity) { + handleUpdateActivity(activity) { debug('inside update') switch (activity.object.type) { - case 'Note': - case 'Article': - return this.dataSource.updatePost(activity) - default: + case 'Note': + case 'Article': + return this.dataSource.updatePost(activity) + default: } } - handleLikeActivity (activity) { + handleLikeActivity(activity) { // TODO differ if activity is an Article/Note/etc. return this.dataSource.createShouted(activity) } - handleDislikeActivity (activity) { + handleDislikeActivity(activity) { // TODO differ if activity is an Article/Note/etc. return this.dataSource.deleteShouted(activity) } - async handleAcceptActivity (activity) { + async handleAcceptActivity(activity) { debug('inside accept') switch (activity.object.type) { - case 'Follow': - const followObject = activity.object - const followingCollectionPage = await this.collections.getFollowingCollectionPage(followObject.actor) - followingCollectionPage.orderedItems.push(followObject.object) - await this.dataSource.saveFollowingCollectionPage(followingCollectionPage) + case 'Follow': + const followObject = activity.object + const followingCollectionPage = await this.collections.getFollowingCollectionPage( + followObject.actor, + ) + followingCollectionPage.orderedItems.push(followObject.object) + await this.dataSource.saveFollowingCollectionPage(followingCollectionPage) } } - getActorObject (url) { + getActorObject(url) { return new Promise((resolve, reject) => { - request({ - url: url, - headers: { - 'Accept': 'application/json' - } - }, (err, response, body) => { - if (err) { - reject(err) - } - resolve(JSON.parse(body)) - }) + request( + { + url: url, + headers: { + Accept: 'application/json', + }, + }, + (err, response, body) => { + if (err) { + reject(err) + } + resolve(JSON.parse(body)) + }, + ) }) } - generateStatusId (slug) { + generateStatusId(slug) { return `https://${this.host}/activitypub/users/${slug}/status/${uuid()}` } - async sendActivity (activity) { + async sendActivity(activity) { delete activity.send const fromName = extractNameFromId(activity.actor) if (Array.isArray(activity.to) && isPublicAddressed(activity)) { debug('is public addressed') const sharedInboxEndpoints = await this.dataSource.getSharedInboxEndpoints() // serve shared inbox endpoints - sharedInboxEndpoints.map((sharedInbox) => { + sharedInboxEndpoints.map(sharedInbox => { return this.trySend(activity, fromName, new URL(sharedInbox).host, sharedInbox) }) - activity.to = activity.to.filter((recipient) => { - return !(isPublicAddressed({ to: recipient })) + activity.to = activity.to.filter(recipient => { + return !isPublicAddressed({ to: recipient }) }) // serve the rest - activity.to.map(async (recipient) => { + activity.to.map(async recipient => { debug('serve rest') const actorObject = await this.getActorObject(recipient) return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox) @@ -207,18 +220,18 @@ export default class ActivityPub { const actorObject = await this.getActorObject(activity.to) return this.trySend(activity, fromName, new URL(activity.to).host, actorObject.inbox) } else if (Array.isArray(activity.to)) { - activity.to.map(async (recipient) => { + activity.to.map(async recipient => { const actorObject = await this.getActorObject(recipient) return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox) }) } } - async trySend (activity, fromName, host, url, tries = 5) { + async trySend(activity, fromName, host, url, tries = 5) { try { return await signAndSend(activity, fromName, host, url) } catch (e) { if (tries > 0) { - setTimeout(function () { + setTimeout(function() { return this.trySend(activity, fromName, host, url, --tries) }, 20000) } diff --git a/backend/src/activitypub/Collections.js b/backend/src/activitypub/Collections.js index 227e1717b..641db596a 100644 --- a/backend/src/activitypub/Collections.js +++ b/backend/src/activitypub/Collections.js @@ -1,28 +1,28 @@ export default class Collections { - constructor (dataSource) { + constructor(dataSource) { this.dataSource = dataSource } - getFollowersCollection (actorId) { + getFollowersCollection(actorId) { return this.dataSource.getFollowersCollection(actorId) } - getFollowersCollectionPage (actorId) { + getFollowersCollectionPage(actorId) { return this.dataSource.getFollowersCollectionPage(actorId) } - getFollowingCollection (actorId) { + getFollowingCollection(actorId) { return this.dataSource.getFollowingCollection(actorId) } - getFollowingCollectionPage (actorId) { + getFollowingCollectionPage(actorId) { return this.dataSource.getFollowingCollectionPage(actorId) } - getOutboxCollection (actorId) { + getOutboxCollection(actorId) { return this.dataSource.getOutboxCollection(actorId) } - getOutboxCollectionPage (actorId) { + getOutboxCollectionPage(actorId) { return this.dataSource.getOutboxCollectionPage(actorId) } } diff --git a/backend/src/activitypub/NitroDataSource.js b/backend/src/activitypub/NitroDataSource.js index 0ab6db091..eea37337a 100644 --- a/backend/src/activitypub/NitroDataSource.js +++ b/backend/src/activitypub/NitroDataSource.js @@ -2,16 +2,10 @@ import { throwErrorIfApolloErrorOccurred, extractIdFromActivityId, extractNameFromId, - constructIdFromName + constructIdFromName, } from './utils' -import { - createOrderedCollection, - createOrderedCollectionPage -} from './utils/collection' -import { - createArticleObject, - isPublicAddressed -} from './utils/activity' +import { createOrderedCollection, createOrderedCollectionPage } from './utils/collection' +import { createArticleObject, isPublicAddressed } from './utils/activity' import crypto from 'crypto' import gql from 'graphql-tag' import { createHttpLink } from 'apollo-link-http' @@ -23,35 +17,36 @@ import trunc from 'trunc-html' const debug = require('debug')('ea:nitro-datasource') export default class NitroDataSource { - constructor (uri) { + constructor(uri) { this.uri = uri const defaultOptions = { query: { fetchPolicy: 'network-only', - errorPolicy: 'all' - } + errorPolicy: 'all', + }, } const link = createHttpLink({ uri: this.uri, fetch: fetch }) // eslint-disable-line const cache = new InMemoryCache() const authLink = setContext((_, { headers }) => { // generate the authentication token (maybe from env? Which user?) - const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJuYW1lIjoiUGV0ZXIgTHVzdGlnIiwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9qb2huY2FmYXp6YS8xMjguanBnIiwiaWQiOiJ1MSIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5vcmciLCJzbHVnIjoicGV0ZXItbHVzdGlnIiwiaWF0IjoxNTUyNDIwMTExLCJleHAiOjE2Mzg4MjAxMTEsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDAwMCIsInN1YiI6InUxIn0.G7An1yeQUViJs-0Qj-Tc-zm0WrLCMB3M02pfPnm6xzw' + const token = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJuYW1lIjoiUGV0ZXIgTHVzdGlnIiwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9qb2huY2FmYXp6YS8xMjguanBnIiwiaWQiOiJ1MSIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5vcmciLCJzbHVnIjoicGV0ZXItbHVzdGlnIiwiaWF0IjoxNTUyNDIwMTExLCJleHAiOjE2Mzg4MjAxMTEsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDAwMCIsInN1YiI6InUxIn0.G7An1yeQUViJs-0Qj-Tc-zm0WrLCMB3M02pfPnm6xzw' // return the headers to the context so httpLink can read them return { headers: { ...headers, - Authorization: token ? `Bearer ${token}` : '' - } + Authorization: token ? `Bearer ${token}` : '', + }, } }) this.client = new ApolloClient({ link: authLink.concat(link), cache: cache, - defaultOptions + defaultOptions, }) } - async getFollowersCollection (actorId) { + async getFollowersCollection(actorId) { const slug = extractNameFromId(actorId) debug(`slug= ${slug}`) const result = await this.client.query({ @@ -61,7 +56,7 @@ export default class NitroDataSource { followedByCount } } - ` + `, }) debug('successfully fetched followers') debug(result.data) @@ -78,7 +73,7 @@ export default class NitroDataSource { } } - async getFollowersCollectionPage (actorId) { + async getFollowersCollectionPage(actorId) { const slug = extractNameFromId(actorId) debug(`getFollowersPage slug = ${slug}`) const result = await this.client.query({ @@ -91,7 +86,7 @@ export default class NitroDataSource { followedByCount } } - ` + `, }) debug(result.data) @@ -104,9 +99,9 @@ export default class NitroDataSource { followersCollection.totalItems = followersCount debug(`followers = ${JSON.stringify(followers, null, 2)}`) await Promise.all( - followers.map(async (follower) => { + followers.map(async follower => { followersCollection.orderedItems.push(constructIdFromName(follower.slug)) - }) + }), ) return followersCollection @@ -115,7 +110,7 @@ export default class NitroDataSource { } } - async getFollowingCollection (actorId) { + async getFollowingCollection(actorId) { const slug = extractNameFromId(actorId) const result = await this.client.query({ query: gql` @@ -124,7 +119,7 @@ export default class NitroDataSource { followingCount } } - ` + `, }) debug(result.data) @@ -141,7 +136,7 @@ export default class NitroDataSource { } } - async getFollowingCollectionPage (actorId) { + async getFollowingCollectionPage(actorId) { const slug = extractNameFromId(actorId) const result = await this.client.query({ query: gql` @@ -153,7 +148,7 @@ export default class NitroDataSource { followingCount } } - ` + `, }) debug(result.data) @@ -166,9 +161,9 @@ export default class NitroDataSource { followingCollection.totalItems = followingCount await Promise.all( - following.map(async (user) => { + following.map(async user => { followingCollection.orderedItems.push(await constructIdFromName(user.slug)) - }) + }), ) return followingCollection @@ -177,7 +172,7 @@ export default class NitroDataSource { } } - async getOutboxCollection (actorId) { + async getOutboxCollection(actorId) { const slug = extractNameFromId(actorId) const result = await this.client.query({ query: gql` @@ -192,7 +187,7 @@ export default class NitroDataSource { } } } - ` + `, }) debug(result.data) @@ -209,7 +204,7 @@ export default class NitroDataSource { } } - async getOutboxCollectionPage (actorId) { + async getOutboxCollectionPage(actorId) { const slug = extractNameFromId(actorId) debug(`inside getting outbox collection page => ${slug}`) const result = await this.client.query({ @@ -232,7 +227,7 @@ export default class NitroDataSource { } } } - ` + `, }) debug(result.data) @@ -243,9 +238,18 @@ export default class NitroDataSource { const outboxCollection = createOrderedCollectionPage(slug, 'outbox') outboxCollection.totalItems = posts.length await Promise.all( - posts.map(async (post) => { - outboxCollection.orderedItems.push(await createArticleObject(post.activityId, post.objectId, post.content, post.author.slug, post.id, post.createdAt)) - }) + posts.map(async post => { + outboxCollection.orderedItems.push( + await createArticleObject( + post.activityId, + post.objectId, + post.content, + post.author.slug, + post.id, + post.createdAt, + ), + ) + }), ) debug('after createNote') @@ -255,7 +259,7 @@ export default class NitroDataSource { } } - async undoFollowActivity (fromActorId, toActorId) { + async undoFollowActivity(fromActorId, toActorId) { const fromUserId = await this.ensureUser(fromActorId) const toUserId = await this.ensureUser(toActorId) const result = await this.client.mutate({ @@ -265,13 +269,13 @@ export default class NitroDataSource { from { name } } } - ` + `, }) debug(`undoFollowActivity result = ${JSON.stringify(result, null, 2)}`) throwErrorIfApolloErrorOccurred(result) } - async saveFollowersCollectionPage (followersCollection, onlyNewestItem = true) { + async saveFollowersCollectionPage(followersCollection, onlyNewestItem = true) { debug('inside saveFollowers') let orderedItems = followersCollection.orderedItems const toUserName = extractNameFromId(followersCollection.id) @@ -279,7 +283,7 @@ export default class NitroDataSource { orderedItems = onlyNewestItem ? [orderedItems.pop()] : orderedItems return Promise.all( - orderedItems.map(async (follower) => { + orderedItems.map(async follower => { debug(`follower = ${follower}`) const fromUserId = await this.ensureUser(follower) debug(`fromUserId = ${fromUserId}`) @@ -291,22 +295,22 @@ export default class NitroDataSource { from { name } } } - ` + `, }) debug(`addUserFollowedBy edge = ${JSON.stringify(result, null, 2)}`) throwErrorIfApolloErrorOccurred(result) debug('saveFollowers: added follow edge successfully') - }) + }), ) } - async saveFollowingCollectionPage (followingCollection, onlyNewestItem = true) { + async saveFollowingCollectionPage(followingCollection, onlyNewestItem = true) { debug('inside saveFollowers') let orderedItems = followingCollection.orderedItems const fromUserName = extractNameFromId(followingCollection.id) const fromUserId = await this.ensureUser(constructIdFromName(fromUserName)) orderedItems = onlyNewestItem ? [orderedItems.pop()] : orderedItems return Promise.all( - orderedItems.map(async (following) => { + orderedItems.map(async following => { debug(`follower = ${following}`) const toUserId = await this.ensureUser(following) debug(`fromUserId = ${fromUserId}`) @@ -318,33 +322,45 @@ export default class NitroDataSource { from { name } } } - ` + `, }) debug(`addUserFollowing edge = ${JSON.stringify(result, null, 2)}`) throwErrorIfApolloErrorOccurred(result) debug('saveFollowing: added follow edge successfully') - }) + }), ) } - async createPost (activity) { + async createPost(activity) { // TODO how to handle the to field? Now the post is just created, doesn't matter who is the recipient // createPost const postObject = activity.object if (!isPublicAddressed(postObject)) { - return debug('createPost: not send to public (sending to specific persons is not implemented yet)') + return debug( + 'createPost: not send to public (sending to specific persons is not implemented yet)', + ) } - const title = postObject.summary ? postObject.summary : postObject.content.split(' ').slice(0, 5).join(' ') + const title = postObject.summary + ? postObject.summary + : postObject.content + .split(' ') + .slice(0, 5) + .join(' ') const postId = extractIdFromActivityId(postObject.id) debug('inside create post') let result = await this.client.mutate({ mutation: gql` mutation { - CreatePost(content: "${postObject.content}", contentExcerpt: "${trunc(postObject.content, 120)}", title: "${title}", id: "${postId}", objectId: "${postObject.id}", activityId: "${activity.id}") { + CreatePost(content: "${postObject.content}", contentExcerpt: "${trunc( + postObject.content, + 120, + )}", title: "${title}", id: "${postId}", objectId: "${postObject.id}", activityId: "${ + activity.id + }") { id } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) @@ -362,13 +378,13 @@ export default class NitroDataSource { } } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) } - async deletePost (activity) { + async deletePost(activity) { const result = await this.client.mutate({ mutation: gql` mutation { @@ -376,28 +392,30 @@ export default class NitroDataSource { title } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) } - async updatePost (activity) { + async updatePost(activity) { const postObject = activity.object const postId = extractIdFromActivityId(postObject.id) const date = postObject.updated ? postObject.updated : new Date().toISOString() const result = await this.client.mutate({ mutation: gql` mutation { - UpdatePost(content: "${postObject.content}", contentExcerpt: "${trunc(postObject.content, 120).html}", id: "${postId}", updatedAt: "${date}") { + UpdatePost(content: "${postObject.content}", contentExcerpt: "${ + trunc(postObject.content, 120).html + }", id: "${postId}", updatedAt: "${date}") { title } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) } - async createShouted (activity) { + async createShouted(activity) { const userId = await this.ensureUser(activity.actor) const postId = extractIdFromActivityId(activity.object) const result = await this.client.mutate({ @@ -409,7 +427,7 @@ export default class NitroDataSource { } } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) if (!result.data.AddUserShouted) { @@ -418,7 +436,7 @@ export default class NitroDataSource { } } - async deleteShouted (activity) { + async deleteShouted(activity) { const userId = await this.ensureUser(activity.actor) const postId = extractIdFromActivityId(activity.object) const result = await this.client.mutate({ @@ -430,7 +448,7 @@ export default class NitroDataSource { } } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) if (!result.data.AddUserShouted) { @@ -439,27 +457,27 @@ export default class NitroDataSource { } } - async getSharedInboxEndpoints () { + async getSharedInboxEndpoints() { const result = await this.client.query({ query: gql` query { - SharedInboxEndpoint { - uri - } + SharedInboxEndpoint { + uri + } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) return result.data.SharedInboxEnpoint } - async addSharedInboxEndpoint (uri) { + async addSharedInboxEndpoint(uri) { try { const result = await this.client.mutate({ mutation: gql` mutation { CreateSharedInboxEndpoint(uri: "${uri}") } - ` + `, }) throwErrorIfApolloErrorOccurred(result) return true @@ -468,16 +486,18 @@ export default class NitroDataSource { } } - async createComment (activity) { + async createComment(activity) { const postObject = activity.object let result = await this.client.mutate({ mutation: gql` mutation { - CreateComment(content: "${postObject.content}", activityId: "${extractIdFromActivityId(activity.id)}") { + CreateComment(content: "${ + postObject.content + }", activityId: "${extractIdFromActivityId(activity.id)}") { id } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) @@ -485,11 +505,13 @@ export default class NitroDataSource { const result2 = await this.client.mutate({ mutation: gql` mutation { - AddCommentAuthor(from: {id: "${result.data.CreateComment.id}"}, to: {id: "${toUserId}"}) { + AddCommentAuthor(from: {id: "${ + result.data.CreateComment.id + }"}, to: {id: "${toUserId}"}) { id } } - ` + `, }) throwErrorIfApolloErrorOccurred(result2) @@ -497,11 +519,13 @@ export default class NitroDataSource { result = await this.client.mutate({ mutation: gql` mutation { - AddCommentPost(from: { id: "${result.data.CreateComment.id}", to: { id: "${postId}" }}) { + AddCommentPost(from: { id: "${ + result.data.CreateComment.id + }", to: { id: "${postId}" }}) { id } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) @@ -513,7 +537,7 @@ export default class NitroDataSource { * @param actorId * @returns {Promise<*>} */ - async ensureUser (actorId) { + async ensureUser(actorId) { debug(`inside ensureUser = ${actorId}`) const name = extractNameFromId(actorId) const queryResult = await this.client.query({ @@ -523,10 +547,14 @@ export default class NitroDataSource { id } } - ` + `, }) - if (queryResult.data && Array.isArray(queryResult.data.User) && queryResult.data.User.length > 0) { + if ( + queryResult.data && + Array.isArray(queryResult.data.User) && + queryResult.data.User.length > 0 + ) { debug('ensureUser: user exists.. return id') // user already exists.. return the id return queryResult.data.User[0].id @@ -534,7 +562,10 @@ export default class NitroDataSource { debug('ensureUser: user not exists.. createUser') // user does not exist.. create it const pw = crypto.randomBytes(16).toString('hex') - const slug = name.toLowerCase().split(' ').join('-') + const slug = name + .toLowerCase() + .split(' ') + .join('-') const result = await this.client.mutate({ mutation: gql` mutation { @@ -542,7 +573,7 @@ export default class NitroDataSource { id } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) diff --git a/backend/src/activitypub/routes/inbox.js b/backend/src/activitypub/routes/inbox.js index f9cfb3794..b31b89ed4 100644 --- a/backend/src/activitypub/routes/inbox.js +++ b/backend/src/activitypub/routes/inbox.js @@ -7,24 +7,24 @@ const router = express.Router() // Shared Inbox endpoint (federated Server) // For now its only able to handle Note Activities!! -router.post('/', async function (req, res, next) { +router.post('/', async function(req, res, next) { debug(`Content-Type = ${req.get('Content-Type')}`) debug(`body = ${JSON.stringify(req.body, null, 2)}`) debug(`Request headers = ${JSON.stringify(req.headers, null, 2)}`) switch (req.body.type) { - case 'Create': - await activityPub.handleCreateActivity(req.body).catch(next) - break - case 'Undo': - await activityPub.handleUndoActivity(req.body).catch(next) - break - case 'Follow': - await activityPub.handleFollowActivity(req.body).catch(next) - break - case 'Delete': - await activityPub.handleDeleteActivity(req.body).catch(next) - break - /* eslint-disable */ + case 'Create': + await activityPub.handleCreateActivity(req.body).catch(next) + break + case 'Undo': + await activityPub.handleUndoActivity(req.body).catch(next) + break + case 'Follow': + await activityPub.handleFollowActivity(req.body).catch(next) + break + case 'Delete': + await activityPub.handleDeleteActivity(req.body).catch(next) + break + /* eslint-disable */ case 'Update': await activityPub.handleUpdateActivity(req.body).catch(next) break diff --git a/backend/src/activitypub/routes/index.js b/backend/src/activitypub/routes/index.js index 24898e766..c7d31f1c4 100644 --- a/backend/src/activitypub/routes/index.js +++ b/backend/src/activitypub/routes/index.js @@ -7,23 +7,21 @@ import verify from './verify' const router = express.Router() -router.use('/.well-known/webFinger', - cors(), - express.urlencoded({ extended: true }), - webFinger -) -router.use('/activitypub/users', +router.use('/.well-known/webFinger', cors(), express.urlencoded({ extended: true }), webFinger) +router.use( + '/activitypub/users', cors(), express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }), express.urlencoded({ extended: true }), - user + user, ) -router.use('/activitypub/inbox', +router.use( + '/activitypub/inbox', cors(), express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }), express.urlencoded({ extended: true }), verify, - inbox + inbox, ) export default router diff --git a/backend/src/activitypub/routes/serveUser.js b/backend/src/activitypub/routes/serveUser.js index f65876741..6f4472235 100644 --- a/backend/src/activitypub/routes/serveUser.js +++ b/backend/src/activitypub/routes/serveUser.js @@ -2,7 +2,7 @@ import { createActor } from '../utils/actor' const gql = require('graphql-tag') const debug = require('debug')('ea:serveUser') -export async function serveUser (req, res, next) { +export async function serveUser(req, res, next) { let name = req.params.name if (name.startsWith('@')) { @@ -10,21 +10,32 @@ export async function serveUser (req, res, next) { } debug(`name = ${name}`) - const result = await req.app.get('ap').dataSource.client.query({ - query: gql` + const result = await req.app + .get('ap') + .dataSource.client.query({ + query: gql` query { User(slug: "${name}") { publicKey } } - ` - }).catch(reason => { debug(`serveUser User fetch error: ${reason}`) }) + `, + }) + .catch(reason => { + debug(`serveUser User fetch error: ${reason}`) + }) if (result.data && Array.isArray(result.data.User) && result.data.User.length > 0) { const publicKey = result.data.User[0].publicKey const actor = createActor(name, publicKey) debug(`actor = ${JSON.stringify(actor, null, 2)}`) - debug(`accepts json = ${req.accepts(['application/activity+json', 'application/ld+json', 'application/json'])}`) + debug( + `accepts json = ${req.accepts([ + 'application/activity+json', + 'application/ld+json', + 'application/json', + ])}`, + ) if (req.accepts(['application/activity+json', 'application/ld+json', 'application/json'])) { return res.json(actor) } else if (req.accepts('text/html')) { diff --git a/backend/src/activitypub/routes/user.js b/backend/src/activitypub/routes/user.js index 017891e61..9dc9b5071 100644 --- a/backend/src/activitypub/routes/user.js +++ b/backend/src/activitypub/routes/user.js @@ -7,7 +7,7 @@ import verify from './verify' const router = express.Router() const debug = require('debug')('ea:user') -router.get('/:name', async function (req, res, next) { +router.get('/:name', async function(req, res, next) { debug('inside user.js -> serveUser') await serveUser(req, res, next) }) @@ -45,24 +45,24 @@ router.get('/:name/outbox', (req, res) => { } }) -router.post('/:name/inbox', verify, async function (req, res, next) { +router.post('/:name/inbox', verify, async function(req, res, next) { debug(`body = ${JSON.stringify(req.body, null, 2)}`) debug(`actorId = ${req.body.actor}`) // const result = await saveActorId(req.body.actor) switch (req.body.type) { - case 'Create': - await activityPub.handleCreateActivity(req.body).catch(next) - break - case 'Undo': - await activityPub.handleUndoActivity(req.body).catch(next) - break - case 'Follow': - await activityPub.handleFollowActivity(req.body).catch(next) - break - case 'Delete': - await activityPub.handleDeleteActivity(req.body).catch(next) - break - /* eslint-disable */ + case 'Create': + await activityPub.handleCreateActivity(req.body).catch(next) + break + case 'Undo': + await activityPub.handleUndoActivity(req.body).catch(next) + break + case 'Follow': + await activityPub.handleFollowActivity(req.body).catch(next) + break + case 'Delete': + await activityPub.handleDeleteActivity(req.body).catch(next) + break + /* eslint-disable */ case 'Update': await activityPub.handleUpdateActivity(req.body).catch(next) break diff --git a/backend/src/activitypub/routes/verify.js b/backend/src/activitypub/routes/verify.js index bb5850b3e..33603805f 100644 --- a/backend/src/activitypub/routes/verify.js +++ b/backend/src/activitypub/routes/verify.js @@ -4,7 +4,12 @@ const debug = require('debug')('ea:verify') export default async (req, res, next) => { debug(`actorId = ${req.body.actor}`) // TODO stop if signature validation fails - if (await verifySignature(`${req.protocol}://${req.hostname}:${req.app.get('port')}${req.originalUrl}`, req.headers)) { + if ( + await verifySignature( + `${req.protocol}://${req.hostname}:${req.app.get('port')}${req.originalUrl}`, + req.headers, + ) + ) { debug('verify = true') next() } else { diff --git a/backend/src/activitypub/routes/webFinger.js b/backend/src/activitypub/routes/webFinger.js index 8def32328..7d52c69cd 100644 --- a/backend/src/activitypub/routes/webFinger.js +++ b/backend/src/activitypub/routes/webFinger.js @@ -4,10 +4,14 @@ import gql from 'graphql-tag' const router = express.Router() -router.get('/', async function (req, res) { +router.get('/', async function(req, res) { const resource = req.query.resource if (!resource || !resource.includes('acct:')) { - return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.') + return res + .status(400) + .send( + 'Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.', + ) } else { const nameAndDomain = resource.replace('acct:', '') const name = nameAndDomain.split('@')[0] @@ -21,7 +25,7 @@ router.get('/', async function (req, res) { slug } } - ` + `, }) } catch (error) { return res.status(500).json({ error }) diff --git a/backend/src/activitypub/security/httpSignature.spec.js b/backend/src/activitypub/security/httpSignature.spec.js index d40c38242..0c6fbb8b5 100644 --- a/backend/src/activitypub/security/httpSignature.spec.js +++ b/backend/src/activitypub/security/httpSignature.spec.js @@ -14,9 +14,9 @@ describe('activityPub/security', () => { privateKey = pair.privateKey publicKey = pair.publicKey headers = { - 'Date': '2019-03-08T14:35:45.759Z', - 'Host': 'democracy-app.de', - 'Content-Type': 'application/json' + Date: '2019-03-08T14:35:45.759Z', + Host: 'democracy-app.de', + 'Content-Type': 'application/json', } }) @@ -27,13 +27,23 @@ describe('activityPub/security', () => { beforeEach(() => { const signer = crypto.createSign('rsa-sha256') - signer.update('(request-target): post /activitypub/users/max/inbox\ndate: 2019-03-08T14:35:45.759Z\nhost: democracy-app.de\ncontent-type: application/json') + signer.update( + '(request-target): post /activitypub/users/max/inbox\ndate: 2019-03-08T14:35:45.759Z\nhost: democracy-app.de\ncontent-type: application/json', + ) signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64') - httpSignature = createSignature({ privateKey, keyId: 'https://human-connection.org/activitypub/users/lea#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase }) + httpSignature = createSignature({ + privateKey, + keyId: 'https://human-connection.org/activitypub/users/lea#main-key', + url: 'https://democracy-app.de/activitypub/users/max/inbox', + headers, + passphrase, + }) }) it('contains keyId', () => { - expect(httpSignature).toContain('keyId="https://human-connection.org/activitypub/users/lea#main-key"') + expect(httpSignature).toContain( + 'keyId="https://human-connection.org/activitypub/users/lea#main-key"', + ) }) it('contains default algorithm "rsa-sha256"', () => { @@ -54,13 +64,19 @@ describe('activityPub/security', () => { let httpSignature beforeEach(() => { - httpSignature = createSignature({ privateKey, keyId: 'http://localhost:4001/activitypub/users/test-user#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase }) + httpSignature = createSignature({ + privateKey, + keyId: 'http://localhost:4001/activitypub/users/test-user#main-key', + url: 'https://democracy-app.de/activitypub/users/max/inbox', + headers, + passphrase, + }) const body = { - 'publicKey': { - 'id': 'https://localhost:4001/activitypub/users/test-user#main-key', - 'owner': 'https://localhost:4001/activitypub/users/test-user', - 'publicKeyPem': publicKey - } + publicKey: { + id: 'https://localhost:4001/activitypub/users/test-user#main-key', + owner: 'https://localhost:4001/activitypub/users/test-user', + publicKeyPem: publicKey, + }, } const mockedRequest = jest.fn((_, callback) => callback(null, null, JSON.stringify(body))) @@ -68,7 +84,9 @@ describe('activityPub/security', () => { }) it('resolves false', async () => { - await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(false) + await expect( + verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers), + ).resolves.toEqual(false) }) describe('valid signature', () => { @@ -77,7 +95,9 @@ describe('activityPub/security', () => { }) it('resolves true', async () => { - await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(true) + await expect( + verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers), + ).resolves.toEqual(true) }) }) }) diff --git a/backend/src/activitypub/security/index.js b/backend/src/activitypub/security/index.js index fdb1e27c6..7f619acbe 100644 --- a/backend/src/activitypub/security/index.js +++ b/backend/src/activitypub/security/index.js @@ -6,42 +6,48 @@ const debug = require('debug')('ea:security') dotenv.config({ path: resolve('src', 'activitypub', '.env') }) -export function generateRsaKeyPair (options = {}) { +export function generateRsaKeyPair(options = {}) { const { passphrase = process.env.PRIVATE_KEY_PASSPHRASE } = options return crypto.generateKeyPairSync('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'spki', - format: 'pem' + format: 'pem', }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', - passphrase - } + passphrase, + }, }) } // signing -export function createSignature (options) { +export function createSignature(options) { const { - privateKey, keyId, url, + privateKey, + keyId, + url, headers = {}, algorithm = 'rsa-sha256', - passphrase = process.env.PRIVATE_KEY_PASSPHRASE + passphrase = process.env.PRIVATE_KEY_PASSPHRASE, } = options - if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) } + if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { + throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) + } const signer = crypto.createSign(algorithm) const signingString = constructSigningString(url, headers) signer.update(signingString) const signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64') - const headersString = Object.keys(headers).reduce((result, key) => { return result + ' ' + key.toLowerCase() }, '') + const headersString = Object.keys(headers).reduce((result, key) => { + return result + ' ' + key.toLowerCase() + }, '') return `keyId="${keyId}",algorithm="${algorithm}",headers="(request-target)${headersString}",signature="${signatureB64}"` } // verifying -export function verifySignature (url, headers) { +export function verifySignature(url, headers) { return new Promise((resolve, reject) => { const signatureHeader = headers['signature'] ? headers['signature'] : headers['Signature'] if (!signatureHeader) { @@ -61,40 +67,47 @@ export function verifySignature (url, headers) { const usedHeaders = headersString.split(' ') const verifyHeaders = {} - Object.keys(headers).forEach((key) => { + Object.keys(headers).forEach(key => { if (usedHeaders.includes(key.toLowerCase())) { verifyHeaders[key.toLowerCase()] = headers[key] } }) const signingString = constructSigningString(url, verifyHeaders) debug(`keyId= ${keyId}`) - request({ - url: keyId, - headers: { - 'Accept': 'application/json' - } - }, (err, response, body) => { - if (err) reject(err) - debug(`body = ${body}`) - const actor = JSON.parse(body) - const publicKeyPem = actor.publicKey.publicKeyPem - resolve(httpVerify(publicKeyPem, signature, signingString, algorithm)) - }) + request( + { + url: keyId, + headers: { + Accept: 'application/json', + }, + }, + (err, response, body) => { + if (err) reject(err) + debug(`body = ${body}`) + const actor = JSON.parse(body) + const publicKeyPem = actor.publicKey.publicKeyPem + resolve(httpVerify(publicKeyPem, signature, signingString, algorithm)) + }, + ) }) } // private: signing -function constructSigningString (url, headers) { +function constructSigningString(url, headers) { const urlObj = new URL(url) - let signingString = `(request-target): post ${urlObj.pathname}${urlObj.search !== '' ? urlObj.search : ''}` + let signingString = `(request-target): post ${urlObj.pathname}${ + urlObj.search !== '' ? urlObj.search : '' + }` return Object.keys(headers).reduce((result, key) => { return result + `\n${key.toLowerCase()}: ${headers[key]}` }, signingString) } // private: verifying -function httpVerify (pubKey, signature, signingString, algorithm) { - if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) } +function httpVerify(pubKey, signature, signingString, algorithm) { + if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { + throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) + } const verifier = crypto.createVerify(algorithm) verifier.update(signingString) return verifier.verify(pubKey, signature, 'base64') @@ -103,14 +116,16 @@ function httpVerify (pubKey, signature, signingString, algorithm) { // private: verifying // This function can be used to extract the signature,headers,algorithm etc. out of the Signature Header. // Just pass what you want as key -function extractKeyValueFromSignatureHeader (signatureHeader, key) { - const keyString = signatureHeader.split(',').filter((el) => { +function extractKeyValueFromSignatureHeader(signatureHeader, key) { + const keyString = signatureHeader.split(',').filter(el => { return !!el.startsWith(key) })[0] let firstEqualIndex = keyString.search('=') // When headers are requested add 17 to the index to remove "(request-target) " from the string - if (key === 'headers') { firstEqualIndex += 17 } + if (key === 'headers') { + firstEqualIndex += 17 + } return keyString.substring(firstEqualIndex + 2, keyString.length - 1) } @@ -151,4 +166,5 @@ export const SUPPORTED_HASH_ALGORITHMS = [ 'sha512WithRSAEncryption', 'ssl3-md5', 'ssl3-sha1', - 'whirlpool'] + 'whirlpool', +] diff --git a/backend/src/activitypub/utils/activity.js b/backend/src/activitypub/utils/activity.js index 57b6dfb83..baf13e1bf 100644 --- a/backend/src/activitypub/utils/activity.js +++ b/backend/src/activitypub/utils/activity.js @@ -6,45 +6,45 @@ import as from 'activitystrea.ms' import gql from 'graphql-tag' const debug = require('debug')('ea:utils:activity') -export function createNoteObject (text, name, id, published) { +export function createNoteObject(text, name, id, published) { const createUuid = crypto.randomBytes(16).toString('hex') return { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${activityPub.endpoint}/activitypub/users/${name}/status/${createUuid}`, - 'type': 'Create', - 'actor': `${activityPub.endpoint}/activitypub/users/${name}`, - 'object': { - 'id': `${activityPub.endpoint}/activitypub/users/${name}/status/${id}`, - 'type': 'Note', - 'published': published, - 'attributedTo': `${activityPub.endpoint}/activitypub/users/${name}`, - 'content': text, - 'to': 'https://www.w3.org/ns/activitystreams#Public' - } + id: `${activityPub.endpoint}/activitypub/users/${name}/status/${createUuid}`, + type: 'Create', + actor: `${activityPub.endpoint}/activitypub/users/${name}`, + object: { + id: `${activityPub.endpoint}/activitypub/users/${name}/status/${id}`, + type: 'Note', + published: published, + attributedTo: `${activityPub.endpoint}/activitypub/users/${name}`, + content: text, + to: 'https://www.w3.org/ns/activitystreams#Public', + }, } } -export async function createArticleObject (activityId, objectId, text, name, id, published) { +export async function createArticleObject(activityId, objectId, text, name, id, published) { const actorId = await getActorId(name) return { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${activityId}`, - 'type': 'Create', - 'actor': `${actorId}`, - 'object': { - 'id': `${objectId}`, - 'type': 'Article', - 'published': published, - 'attributedTo': `${actorId}`, - 'content': text, - 'to': 'https://www.w3.org/ns/activitystreams#Public' - } + id: `${activityId}`, + type: 'Create', + actor: `${actorId}`, + object: { + id: `${objectId}`, + type: 'Article', + published: published, + attributedTo: `${actorId}`, + content: text, + to: 'https://www.w3.org/ns/activitystreams#Public', + }, } } -export async function getActorId (name) { +export async function getActorId(name) { const result = await activityPub.dataSource.client.query({ query: gql` query { @@ -52,7 +52,7 @@ export async function getActorId (name) { actorId } } - ` + `, }) throwErrorIfApolloErrorOccurred(result) if (Array.isArray(result.data.User) && result.data.User[0]) { @@ -62,9 +62,12 @@ export async function getActorId (name) { } } -export function sendAcceptActivity (theBody, name, targetDomain, url) { +export function sendAcceptActivity(theBody, name, targetDomain, url) { as.accept() - .id(`${activityPub.endpoint}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex')) + .id( + `${activityPub.endpoint}/activitypub/users/${name}/status/` + + crypto.randomBytes(16).toString('hex'), + ) .actor(`${activityPub.endpoint}/activitypub/users/${name}`) .object(theBody) .prettyWrite((err, doc) => { @@ -77,9 +80,12 @@ export function sendAcceptActivity (theBody, name, targetDomain, url) { }) } -export function sendRejectActivity (theBody, name, targetDomain, url) { +export function sendRejectActivity(theBody, name, targetDomain, url) { as.reject() - .id(`${activityPub.endpoint}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex')) + .id( + `${activityPub.endpoint}/activitypub/users/${name}/status/` + + crypto.randomBytes(16).toString('hex'), + ) .actor(`${activityPub.endpoint}/activitypub/users/${name}`) .object(theBody) .prettyWrite((err, doc) => { @@ -92,7 +98,7 @@ export function sendRejectActivity (theBody, name, targetDomain, url) { }) } -export function isPublicAddressed (postObject) { +export function isPublicAddressed(postObject) { if (typeof postObject.to === 'string') { postObject.to = [postObject.to] } @@ -102,7 +108,9 @@ export function isPublicAddressed (postObject) { if (Array.isArray(postObject)) { postObject.to = postObject } - return postObject.to.includes('Public') || + return ( + postObject.to.includes('Public') || postObject.to.includes('as:Public') || postObject.to.includes('https://www.w3.org/ns/activitystreams#Public') + ) } diff --git a/backend/src/activitypub/utils/actor.js b/backend/src/activitypub/utils/actor.js index 27612517b..a08065778 100644 --- a/backend/src/activitypub/utils/actor.js +++ b/backend/src/activitypub/utils/actor.js @@ -1,41 +1,38 @@ import { activityPub } from '../ActivityPub' -export function createActor (name, pubkey) { +export function createActor(name, pubkey) { return { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], - 'id': `${activityPub.endpoint}/activitypub/users/${name}`, - 'type': 'Person', - 'preferredUsername': `${name}`, - 'name': `${name}`, - 'following': `${activityPub.endpoint}/activitypub/users/${name}/following`, - 'followers': `${activityPub.endpoint}/activitypub/users/${name}/followers`, - 'inbox': `${activityPub.endpoint}/activitypub/users/${name}/inbox`, - 'outbox': `${activityPub.endpoint}/activitypub/users/${name}/outbox`, - 'url': `${activityPub.endpoint}/activitypub/@${name}`, - 'endpoints': { - 'sharedInbox': `${activityPub.endpoint}/activitypub/inbox` + '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], + id: `${activityPub.endpoint}/activitypub/users/${name}`, + type: 'Person', + preferredUsername: `${name}`, + name: `${name}`, + following: `${activityPub.endpoint}/activitypub/users/${name}/following`, + followers: `${activityPub.endpoint}/activitypub/users/${name}/followers`, + inbox: `${activityPub.endpoint}/activitypub/users/${name}/inbox`, + outbox: `${activityPub.endpoint}/activitypub/users/${name}/outbox`, + url: `${activityPub.endpoint}/activitypub/@${name}`, + endpoints: { + sharedInbox: `${activityPub.endpoint}/activitypub/inbox`, + }, + publicKey: { + id: `${activityPub.endpoint}/activitypub/users/${name}#main-key`, + owner: `${activityPub.endpoint}/activitypub/users/${name}`, + publicKeyPem: pubkey, }, - 'publicKey': { - 'id': `${activityPub.endpoint}/activitypub/users/${name}#main-key`, - 'owner': `${activityPub.endpoint}/activitypub/users/${name}`, - 'publicKeyPem': pubkey - } } } -export function createWebFinger (name) { +export function createWebFinger(name) { const { host } = new URL(activityPub.endpoint) return { - 'subject': `acct:${name}@${host}`, - 'links': [ + subject: `acct:${name}@${host}`, + links: [ { - 'rel': 'self', - 'type': 'application/activity+json', - 'href': `${activityPub.endpoint}/activitypub/users/${name}` - } - ] + rel: 'self', + type: 'application/activity+json', + href: `${activityPub.endpoint}/activitypub/users/${name}`, + }, + ], } } diff --git a/backend/src/activitypub/utils/collection.js b/backend/src/activitypub/utils/collection.js index e3a63c74d..29cf69ac2 100644 --- a/backend/src/activitypub/utils/collection.js +++ b/backend/src/activitypub/utils/collection.js @@ -2,68 +2,71 @@ import { activityPub } from '../ActivityPub' import { constructIdFromName } from './index' const debug = require('debug')('ea:utils:collections') -export function createOrderedCollection (name, collectionName) { +export function createOrderedCollection(name, collectionName) { return { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`, - 'summary': `${name}s ${collectionName} collection`, - 'type': 'OrderedCollection', - 'first': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`, - 'totalItems': 0 + id: `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`, + summary: `${name}s ${collectionName} collection`, + type: 'OrderedCollection', + first: `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`, + totalItems: 0, } } -export function createOrderedCollectionPage (name, collectionName) { +export function createOrderedCollectionPage(name, collectionName) { return { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`, - 'summary': `${name}s ${collectionName} collection`, - 'type': 'OrderedCollectionPage', - 'totalItems': 0, - 'partOf': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`, - 'orderedItems': [] + id: `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`, + summary: `${name}s ${collectionName} collection`, + type: 'OrderedCollectionPage', + totalItems: 0, + partOf: `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`, + orderedItems: [], } } -export function sendCollection (collectionName, req, res) { +export function sendCollection(collectionName, req, res) { const name = req.params.name const id = constructIdFromName(name) switch (collectionName) { - case 'followers': - attachThenCatch(activityPub.collections.getFollowersCollection(id), res) - break + case 'followers': + attachThenCatch(activityPub.collections.getFollowersCollection(id), res) + break - case 'followersPage': - attachThenCatch(activityPub.collections.getFollowersCollectionPage(id), res) - break + case 'followersPage': + attachThenCatch(activityPub.collections.getFollowersCollectionPage(id), res) + break - case 'following': - attachThenCatch(activityPub.collections.getFollowingCollection(id), res) - break + case 'following': + attachThenCatch(activityPub.collections.getFollowingCollection(id), res) + break - case 'followingPage': - attachThenCatch(activityPub.collections.getFollowingCollectionPage(id), res) - break + case 'followingPage': + attachThenCatch(activityPub.collections.getFollowingCollectionPage(id), res) + break - case 'outbox': - attachThenCatch(activityPub.collections.getOutboxCollection(id), res) - break + case 'outbox': + attachThenCatch(activityPub.collections.getOutboxCollection(id), res) + break - case 'outboxPage': - attachThenCatch(activityPub.collections.getOutboxCollectionPage(id), res) - break + case 'outboxPage': + attachThenCatch(activityPub.collections.getOutboxCollectionPage(id), res) + break - default: - res.status(500).end() + default: + res.status(500).end() } } -function attachThenCatch (promise, res) { +function attachThenCatch(promise, res) { return promise - .then((collection) => { - res.status(200).contentType('application/activity+json').send(collection) + .then(collection => { + res + .status(200) + .contentType('application/activity+json') + .send(collection) }) - .catch((err) => { + .catch(err => { debug(`error getting a Collection: = ${err}`) res.status(500).end() }) diff --git a/backend/src/activitypub/utils/index.js b/backend/src/activitypub/utils/index.js index a83dcc829..ee7ae2606 100644 --- a/backend/src/activitypub/utils/index.js +++ b/backend/src/activitypub/utils/index.js @@ -4,7 +4,7 @@ import { createSignature } from '../security' import request from 'request' const debug = require('debug')('ea:utils') -export function extractNameFromId (uri) { +export function extractNameFromId(uri) { const urlObject = new URL(uri) const pathname = urlObject.pathname const splitted = pathname.split('/') @@ -12,28 +12,30 @@ export function extractNameFromId (uri) { return splitted[splitted.indexOf('users') + 1] } -export function extractIdFromActivityId (uri) { +export function extractIdFromActivityId(uri) { const urlObject = new URL(uri) const pathname = urlObject.pathname const splitted = pathname.split('/') return splitted[splitted.indexOf('status') + 1] } -export function constructIdFromName (name, fromDomain = activityPub.endpoint) { +export function constructIdFromName(name, fromDomain = activityPub.endpoint) { return `${fromDomain}/activitypub/users/${name}` } -export function extractDomainFromUrl (url) { +export function extractDomainFromUrl(url) { return new URL(url).host } -export function throwErrorIfApolloErrorOccurred (result) { +export function throwErrorIfApolloErrorOccurred(result) { if (result.error && (result.error.message || result.error.errors)) { - throw new Error(`${result.error.message ? result.error.message : result.error.errors[0].message}`) + throw new Error( + `${result.error.message ? result.error.message : result.error.errors[0].message}`, + ) } } -export function signAndSend (activity, fromName, targetDomain, url) { +export function signAndSend(activity, fromName, targetDomain, url) { // fix for development: replace with http url = url.indexOf('localhost') > -1 ? url.replace('https', 'http') : url debug(`passhprase = ${process.env.PRIVATE_KEY_PASSPHRASE}`) @@ -47,7 +49,7 @@ export function signAndSend (activity, fromName, targetDomain, url) { privateKey } } - ` + `, }) if (result.error) { @@ -69,34 +71,38 @@ export function signAndSend (activity, fromName, targetDomain, url) { const date = new Date().toUTCString() debug(`url = ${url}`) - request({ - url: url, - headers: { - 'Host': targetDomain, - 'Date': date, - 'Signature': createSignature({ privateKey, - keyId: `${activityPub.endpoint}/activitypub/users/${fromName}#main-key`, - url, - headers: { - 'Host': targetDomain, - 'Date': date, - 'Content-Type': 'application/activity+json' - } - }), - 'Content-Type': 'application/activity+json' + request( + { + url: url, + headers: { + Host: targetDomain, + Date: date, + Signature: createSignature({ + privateKey, + keyId: `${activityPub.endpoint}/activitypub/users/${fromName}#main-key`, + url, + headers: { + Host: targetDomain, + Date: date, + 'Content-Type': 'application/activity+json', + }, + }), + 'Content-Type': 'application/activity+json', + }, + method: 'POST', + body: JSON.stringify(parsedActivity), }, - method: 'POST', - body: JSON.stringify(parsedActivity) - }, (error, response) => { - if (error) { - debug(`Error = ${JSON.stringify(error, null, 2)}`) - reject(error) - } else { - debug('Response Headers:', JSON.stringify(response.headers, null, 2)) - debug('Response Body:', JSON.stringify(response.body, null, 2)) - resolve() - } - }) + (error, response) => { + if (error) { + debug(`Error = ${JSON.stringify(error, null, 2)}`) + reject(error) + } else { + debug('Response Headers:', JSON.stringify(response.headers, null, 2)) + debug('Response Body:', JSON.stringify(response.body, null, 2)) + resolve() + } + }, + ) } }) } diff --git a/backend/src/bootstrap/directives.js b/backend/src/bootstrap/directives.js index 8c392ed46..93a7574fb 100644 --- a/backend/src/bootstrap/directives.js +++ b/backend/src/bootstrap/directives.js @@ -1,15 +1,11 @@ import { GraphQLLowerCaseDirective, GraphQLTrimDirective, - GraphQLDefaultToDirective + GraphQLDefaultToDirective, } from 'graphql-custom-directives' -export default function applyDirectives (augmentedSchema) { - const directives = [ - GraphQLLowerCaseDirective, - GraphQLTrimDirective, - GraphQLDefaultToDirective - ] +export default function applyDirectives(augmentedSchema) { + const directives = [GraphQLLowerCaseDirective, GraphQLTrimDirective, GraphQLDefaultToDirective] augmentedSchema._directives.push.apply(augmentedSchema._directives, directives) return augmentedSchema diff --git a/backend/src/bootstrap/neo4j.js b/backend/src/bootstrap/neo4j.js index 935449a0a..292983359 100644 --- a/backend/src/bootstrap/neo4j.js +++ b/backend/src/bootstrap/neo4j.js @@ -5,11 +5,11 @@ dotenv.config() let driver -export function getDriver (options = {}) { +export function getDriver(options = {}) { const { uri = process.env.NEO4J_URI || 'bolt://localhost:7687', username = process.env.NEO4J_USERNAME || 'neo4j', - password = process.env.NEO4J_PASSWORD || 'neo4j' + password = process.env.NEO4J_PASSWORD || 'neo4j', } = options if (!driver) { driver = neo4j.driver(uri, neo4j.auth.basic(username, password)) diff --git a/backend/src/bootstrap/scalars.js b/backend/src/bootstrap/scalars.js index 813bd5051..eb6d3739b 100644 --- a/backend/src/bootstrap/scalars.js +++ b/backend/src/bootstrap/scalars.js @@ -1,10 +1,6 @@ -import { - GraphQLDate, - GraphQLTime, - GraphQLDateTime -} from 'graphql-iso-date' +import { GraphQLDate, GraphQLTime, GraphQLDateTime } from 'graphql-iso-date' -export default function applyScalars (augmentedSchema) { +export default function applyScalars(augmentedSchema) { augmentedSchema._typeMap.Date = GraphQLDate augmentedSchema._typeMap.Time = GraphQLTime augmentedSchema._typeMap.DateTime = GraphQLDateTime diff --git a/backend/src/graphql-schema.js b/backend/src/graphql-schema.js index bad277721..0942b2381 100644 --- a/backend/src/graphql-schema.js +++ b/backend/src/graphql-schema.js @@ -14,9 +14,7 @@ import notifications from './resolvers/notifications' import comments from './resolvers/comments' export const typeDefs = fs - .readFileSync( - process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql') - ) + .readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')) .toString('utf-8') export const resolvers = { @@ -24,7 +22,7 @@ export const resolvers = { ...statistics.Query, ...userManagement.Query, ...notifications.Query, - ...comments.Query + ...comments.Query, }, Mutation: { ...userManagement.Mutation, @@ -36,6 +34,6 @@ export const resolvers = { ...rewards.Mutation, ...socialMedia.Mutation, ...notifications.Mutation, - ...comments.Mutation - } + ...comments.Mutation, + }, } diff --git a/backend/src/helpers/asyncForEach.js b/backend/src/helpers/asyncForEach.js index 1f05ea915..5577cce14 100644 --- a/backend/src/helpers/asyncForEach.js +++ b/backend/src/helpers/asyncForEach.js @@ -5,7 +5,7 @@ * @param callback * @returns {Promise} */ -async function asyncForEach (array, callback) { +async function asyncForEach(array, callback) { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array) } diff --git a/backend/src/helpers/walkRecursive.js b/backend/src/helpers/walkRecursive.js index 2f60de2a4..db9a4c703 100644 --- a/backend/src/helpers/walkRecursive.js +++ b/backend/src/helpers/walkRecursive.js @@ -1,10 +1,10 @@ /** - * iterate through all fields and replace it with the callback result - * @property data Array - * @property fields Array - * @property callback Function - */ -function walkRecursive (data, fields, callback, _key) { + * iterate through all fields and replace it with the callback result + * @property data Array + * @property fields Array + * @property callback Function + */ +function walkRecursive(data, fields, callback, _key) { if (!Array.isArray(fields)) { throw new Error('please provide an fields array for the walkRecursive helper') } diff --git a/backend/src/index.js b/backend/src/index.js index 843639aa8..2095d171f 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -2,7 +2,7 @@ import createServer from './server' import ActivityPub from './activitypub/ActivityPub' const serverConfig = { - port: process.env.GRAPHQL_PORT || 4000 + port: process.env.GRAPHQL_PORT || 4000, // cors: { // credentials: true, // origin: [process.env.CLIENT_URI] // your frontend url. diff --git a/backend/src/jest/helpers.js b/backend/src/jest/helpers.js index 0d358ed40..d07bc9ad1 100644 --- a/backend/src/jest/helpers.js +++ b/backend/src/jest/helpers.js @@ -4,13 +4,13 @@ import { request } from 'graphql-request' // not to be confused with the seeder host export const host = 'http://127.0.0.1:4123' -export async function login ({ email, password }) { +export async function login({ email, password }) { const mutation = ` mutation { login(email:"${email}", password:"${password}") }` const response = await request(host, mutation) return { - authorization: `Bearer ${response.login}` + authorization: `Bearer ${response.login}`, } } diff --git a/backend/src/jwt/decode.js b/backend/src/jwt/decode.js index e8305a83b..d4485952d 100644 --- a/backend/src/jwt/decode.js +++ b/backend/src/jwt/decode.js @@ -7,7 +7,7 @@ export default async (driver, authorizationHeader) => { try { const decoded = await jwt.verify(token, process.env.JWT_SECRET) id = decoded.sub - } catch { + } catch (err) { return null } const session = driver.session() @@ -18,13 +18,13 @@ export default async (driver, authorizationHeader) => { ` const result = await session.run(query, { id }) session.close() - const [currentUser] = await result.records.map((record) => { + const [currentUser] = await result.records.map(record => { return record.get('user') }) if (!currentUser) return null if (currentUser.disabled) return null return { token, - ...currentUser + ...currentUser, } } diff --git a/backend/src/jwt/encode.js b/backend/src/jwt/encode.js index f32fc12da..49aa17bd0 100644 --- a/backend/src/jwt/encode.js +++ b/backend/src/jwt/encode.js @@ -1,14 +1,13 @@ - import jwt from 'jsonwebtoken' import ms from 'ms' // Generate an Access Token for the given User ID -export default function encode (user) { +export default function encode(user) { const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: ms('1d'), issuer: process.env.GRAPHQL_URI, audience: process.env.CLIENT_URI, - subject: user.id.toString() + subject: user.id.toString(), }) // jwt.verifySignature(token, process.env.JWT_SECRET, (err, data) => { // console.log('token verification:', err, data) diff --git a/backend/src/middleware/activityPubMiddleware.js b/backend/src/middleware/activityPubMiddleware.js index dcb5ae93c..43da21e26 100644 --- a/backend/src/middleware/activityPubMiddleware.js +++ b/backend/src/middleware/activityPubMiddleware.js @@ -22,13 +22,15 @@ export default { .id(`${actorId}/status/${args.activityId}`) .actor(`${actorId}`) .object( - as.article() + as + .article() .id(`${actorId}/status/${post.id}`) .content(post.content) .to('https://www.w3.org/ns/activitystreams#Public') .publishedNow() - .attributedTo(`${actorId}`) - ).prettyWrite((err, doc) => { + .attributedTo(`${actorId}`), + ) + .prettyWrite((err, doc) => { if (err) { reject(err) } else { @@ -51,6 +53,6 @@ export default { Object.assign(args, keys) args.actorId = `${activityPub.host}/activitypub/users/${args.slug}` return resolve(root, args, context, info) - } - } + }, + }, } diff --git a/backend/src/middleware/dateTimeMiddleware.js b/backend/src/middleware/dateTimeMiddleware.js index 11b6498a4..ac6e0ac4a 100644 --- a/backend/src/middleware/dateTimeMiddleware.js +++ b/backend/src/middleware/dateTimeMiddleware.js @@ -1,9 +1,9 @@ const setCreatedAt = (resolve, root, args, context, info) => { - args.createdAt = (new Date()).toISOString() + args.createdAt = new Date().toISOString() return resolve(root, args, context, info) } const setUpdatedAt = (resolve, root, args, context, info) => { - args.updatedAt = (new Date()).toISOString() + args.updatedAt = new Date().toISOString() return resolve(root, args, context, info) } @@ -18,6 +18,6 @@ export default { UpdatePost: setUpdatedAt, UpdateComment: setUpdatedAt, UpdateOrganization: setUpdatedAt, - UpdateNotification: setUpdatedAt - } + UpdateNotification: setUpdatedAt, + }, } diff --git a/backend/src/middleware/excerptMiddleware.js b/backend/src/middleware/excerptMiddleware.js index 544dc3529..3b3a27c2c 100644 --- a/backend/src/middleware/excerptMiddleware.js +++ b/backend/src/middleware/excerptMiddleware.js @@ -31,6 +31,6 @@ export default { args.descriptionExcerpt = trunc(args.description, 120).html const result = await resolve(root, args, context, info) return result - } - } + }, + }, } diff --git a/backend/src/middleware/fixImageUrlsMiddleware.js b/backend/src/middleware/fixImageUrlsMiddleware.js index e5dc47a6d..c930915bf 100644 --- a/backend/src/middleware/fixImageUrlsMiddleware.js +++ b/backend/src/middleware/fixImageUrlsMiddleware.js @@ -1,21 +1,24 @@ - const legacyUrls = [ 'https://api-alpha.human-connection.org', 'https://staging-api.human-connection.org', - 'http://localhost:3000' + 'http://localhost:3000', ] -export const fixUrl = (url) => { - legacyUrls.forEach((legacyUrl) => { +export const fixUrl = url => { + legacyUrls.forEach(legacyUrl => { url = url.replace(legacyUrl, '/api') }) return url } -const checkUrl = (thing) => { - return thing && typeof thing === 'string' && legacyUrls.find((legacyUrl) => { - return thing.indexOf(legacyUrl) === 0 - }) +const checkUrl = thing => { + return ( + thing && + typeof thing === 'string' && + legacyUrls.find(legacyUrl => { + return thing.indexOf(legacyUrl) === 0 + }) + ) } export const fixImageURLs = (result, recursive) => { @@ -41,5 +44,5 @@ export default { Query: async (resolve, root, args, context, info) => { let result = await resolve(root, args, context, info) return fixImageURLs(result) - } + }, } diff --git a/backend/src/middleware/fixImageUrlsMiddleware.spec.js b/backend/src/middleware/fixImageUrlsMiddleware.spec.js index 89d2a520d..b2d808dd9 100644 --- a/backend/src/middleware/fixImageUrlsMiddleware.spec.js +++ b/backend/src/middleware/fixImageUrlsMiddleware.spec.js @@ -3,15 +3,21 @@ import { fixImageURLs } from './fixImageUrlsMiddleware' describe('fixImageURLs', () => { describe('image url of legacy alpha', () => { it('removes domain', () => { - const url = 'https://api-alpha.human-connection.org/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png' - expect(fixImageURLs(url)).toEqual('/api/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png') + const url = + 'https://api-alpha.human-connection.org/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png' + expect(fixImageURLs(url)).toEqual( + '/api/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png', + ) }) }) describe('image url of legacy staging', () => { it('removes domain', () => { - const url = 'https://staging-api.human-connection.org/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg' - expect(fixImageURLs(url)).toEqual('/api/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg') + const url = + 'https://staging-api.human-connection.org/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg' + expect(fixImageURLs(url)).toEqual( + '/api/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg', + ) }) }) @@ -24,7 +30,7 @@ describe('fixImageURLs', () => { describe('some string', () => { it('returns untouched', () => {}) - const string = 'Yeah I\'m a String' + const string = "Yeah I'm a String" expect(fixImageURLs(string)).toEqual(string) }) }) diff --git a/backend/src/middleware/includedFieldsMiddleware.js b/backend/src/middleware/includedFieldsMiddleware.js index 5dd63cd3c..cd7a74f4e 100644 --- a/backend/src/middleware/includedFieldsMiddleware.js +++ b/backend/src/middleware/includedFieldsMiddleware.js @@ -2,21 +2,21 @@ import cloneDeep from 'lodash/cloneDeep' const _includeFieldsRecursively = (selectionSet, includedFields) => { if (!selectionSet) return - includedFields.forEach((includedField) => { + includedFields.forEach(includedField => { selectionSet.selections.unshift({ kind: 'Field', - name: { kind: 'Name', value: includedField } + name: { kind: 'Name', value: includedField }, }) }) - selectionSet.selections.forEach((selection) => { + selectionSet.selections.forEach(selection => { _includeFieldsRecursively(selection.selectionSet, includedFields) }) } -const includeFieldsRecursively = (includedFields) => { +const includeFieldsRecursively = includedFields => { return (resolve, root, args, context, resolveInfo) => { const copy = cloneDeep(resolveInfo) - copy.fieldNodes.forEach((fieldNode) => { + copy.fieldNodes.forEach(fieldNode => { _includeFieldsRecursively(fieldNode.selectionSet, includedFields) }) return resolve(root, args, context, copy) @@ -25,5 +25,5 @@ const includeFieldsRecursively = (includedFields) => { export default { Query: includeFieldsRecursively(['id', 'createdAt', 'disabled', 'deleted']), - Mutation: includeFieldsRecursively(['id', 'createdAt', 'disabled', 'deleted']) + Mutation: includeFieldsRecursively(['id', 'createdAt', 'disabled', 'deleted']), } diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index 17b9d63fb..bef6ceac9 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -26,7 +26,7 @@ export default schema => { softDeleteMiddleware, userMiddleware, includedFieldsMiddleware, - orderByMiddleware + orderByMiddleware, ] // add permisions middleware at the first position (unless we're seeding) @@ -35,7 +35,8 @@ export default schema => { const DISABLED_MIDDLEWARES = process.env.DISABLED_MIDDLEWARES || '' const disabled = DISABLED_MIDDLEWARES.split(',') if (!disabled.includes('activityPub')) middleware.unshift(activityPubMiddleware) - if (!disabled.includes('permissions')) middleware.unshift(permissionsMiddleware.generate(schema)) + if (!disabled.includes('permissions')) + middleware.unshift(permissionsMiddleware.generate(schema)) } return middleware } diff --git a/backend/src/middleware/nodes/locations.js b/backend/src/middleware/nodes/locations.js index 735b047dd..a0adeb57f 100644 --- a/backend/src/middleware/nodes/locations.js +++ b/backend/src/middleware/nodes/locations.js @@ -1,4 +1,3 @@ - import request from 'request' import { UserInputError } from 'apollo-server' import isEmpty from 'lodash/isEmpty' @@ -6,7 +5,7 @@ import asyncForEach from '../../helpers/asyncForEach' const fetch = url => { return new Promise((resolve, reject) => { - request(url, function (error, response, body) { + request(url, function(error, response, body) { if (error) { reject(error) } else { @@ -16,16 +15,7 @@ const fetch = url => { }) } -const locales = [ - 'en', - 'de', - 'fr', - 'nl', - 'it', - 'es', - 'pt', - 'pl' -] +const locales = ['en', 'de', 'fr', 'nl', 'it', 'es', 'pt', 'pl'] const createLocation = async (session, mapboxData) => { const data = { @@ -39,21 +29,22 @@ const createLocation = async (session, mapboxData) => { namePT: mapboxData.text_pt, namePL: mapboxData.text_pl, type: mapboxData.id.split('.')[0].toLowerCase(), - lat: (mapboxData.center && mapboxData.center.length) ? mapboxData.center[0] : null, - lng: (mapboxData.center && mapboxData.center.length) ? mapboxData.center[1] : null + lat: mapboxData.center && mapboxData.center.length ? mapboxData.center[0] : null, + lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null, } - let query = 'MERGE (l:Location {id: $id}) ' + - 'SET l.name = $nameEN, ' + - 'l.nameEN = $nameEN, ' + - 'l.nameDE = $nameDE, ' + - 'l.nameFR = $nameFR, ' + - 'l.nameNL = $nameNL, ' + - 'l.nameIT = $nameIT, ' + - 'l.nameES = $nameES, ' + - 'l.namePT = $namePT, ' + - 'l.namePL = $namePL, ' + - 'l.type = $type' + let query = + 'MERGE (l:Location {id: $id}) ' + + 'SET l.name = $nameEN, ' + + 'l.nameEN = $nameEN, ' + + 'l.nameDE = $nameDE, ' + + 'l.nameFR = $nameFR, ' + + 'l.nameNL = $nameNL, ' + + 'l.nameIT = $nameIT, ' + + 'l.nameES = $nameES, ' + + 'l.namePT = $namePT, ' + + 'l.namePL = $namePL, ' + + 'l.type = $type' if (data.lat && data.lng) { query += ', l.lat = $lat, l.lng = $lng' @@ -68,7 +59,11 @@ const createOrUpdateLocations = async (userId, locationName, driver) => { return } const mapboxToken = process.env.MAPBOX_TOKEN - const res = await fetch(`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(locationName)}.json?access_token=${mapboxToken}&types=region,place,country&language=${locales.join(',')}`) + const res = await fetch( + `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent( + locationName, + )}.json?access_token=${mapboxToken}&types=region,place,country&language=${locales.join(',')}`, + ) if (!res || !res.features || !res.features[0]) { throw new UserInputError('locationName is invalid') @@ -100,24 +95,29 @@ const createOrUpdateLocations = async (userId, locationName, driver) => { await session.run( 'MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) ' + - 'MERGE (child)<-[:IS_IN]-(parent) ' + - 'RETURN child.id, parent.id', { + 'MERGE (child)<-[:IS_IN]-(parent) ' + + 'RETURN child.id, parent.id', + { parentId: parent.id, - childId: ctx.id - }) + childId: ctx.id, + }, + ) parent = ctx }) } // delete all current locations from user await session.run('MATCH (u:User {id: $userId})-[r:IS_IN]->(l:Location) DETACH DELETE r', { - userId: userId + userId: userId, }) // connect user with location - await session.run('MATCH (u:User {id: $userId}), (l:Location {id: $locationId}) MERGE (u)-[:IS_IN]->(l) RETURN l.id, u.id', { - userId: userId, - locationId: data.id - }) + await session.run( + 'MATCH (u:User {id: $userId}), (l:Location {id: $locationId}) MERGE (u)-[:IS_IN]->(l) RETURN l.id, u.id', + { + userId: userId, + locationId: data.id, + }, + ) session.close() } diff --git a/backend/src/middleware/notifications/extractMentions.js b/backend/src/middleware/notifications/extractMentions.js index f2b28444f..d6fa8ac3a 100644 --- a/backend/src/middleware/notifications/extractMentions.js +++ b/backend/src/middleware/notifications/extractMentions.js @@ -1,13 +1,15 @@ import cheerio from 'cheerio' const ID_REGEX = /\/profile\/([\w\-.!~*'"(),]+)/g -export default function (content) { +export default function(content) { const $ = cheerio.load(content) - const urls = $('.mention').map((_, el) => { - return $(el).attr('href') - }).get() + const urls = $('.mention') + .map((_, el) => { + return $(el).attr('href') + }) + .get() const ids = [] - urls.forEach((url) => { + urls.forEach(url => { let match while ((match = ID_REGEX.exec(url)) != null) { ids.push(match[1]) diff --git a/backend/src/middleware/notifications/extractMentions.spec.js b/backend/src/middleware/notifications/extractMentions.spec.js index 625b1d8fe..d55c492ce 100644 --- a/backend/src/middleware/notifications/extractMentions.spec.js +++ b/backend/src/middleware/notifications/extractMentions.spec.js @@ -3,41 +3,48 @@ import extractIds from './extractMentions' describe('extract', () => { describe('searches through links', () => { it('ignores links without .mention class', () => { - const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' expect(extractIds(content)).toEqual([]) }) describe('given a link with .mention class', () => { it('extracts ids', () => { - const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' expect(extractIds(content)).toEqual(['u2', 'u3']) }) describe('handles links', () => { it('with slug and id', () => { - const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' expect(extractIds(content)).toEqual(['u2', 'u3']) }) it('with domains', () => { - const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' expect(extractIds(content)).toEqual(['u2', 'u3']) }) it('special characters', () => { - const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' expect(extractIds(content)).toEqual(['u!*(),2', 'u.~-3']) }) }) describe('does not crash if', () => { it('`href` contains no user id', () => { - const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' expect(extractIds(content)).toEqual([]) }) it('`href` is empty or invalid', () => { - const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' expect(extractIds(content)).toEqual([]) }) }) diff --git a/backend/src/middleware/notifications/index.js b/backend/src/middleware/notifications/index.js index 942eb588d..866a4376a 100644 --- a/backend/src/middleware/notifications/index.js +++ b/backend/src/middleware/notifications/index.js @@ -8,7 +8,7 @@ const notify = async (resolve, root, args, context, resolveInfo) => { const session = context.driver.session() const { id: postId } = post - const createdAt = (new Date()).toISOString() + const createdAt = new Date().toISOString() const cypher = ` match(u:User) where u.id in $ids match(p:Post) where p.id = $postId @@ -25,6 +25,6 @@ const notify = async (resolve, root, args, context, resolveInfo) => { export default { Mutation: { CreatePost: notify, - UpdatePost: notify - } + UpdatePost: notify, + }, } diff --git a/backend/src/middleware/notifications/spec.js b/backend/src/middleware/notifications/spec.js index 786ee7115..65212e544 100644 --- a/backend/src/middleware/notifications/spec.js +++ b/backend/src/middleware/notifications/spec.js @@ -11,7 +11,7 @@ beforeEach(async () => { name: 'Al Capone', slug: 'al-capone', email: 'test@example.org', - password: '1234' + password: '1234', }) }) @@ -47,7 +47,7 @@ describe('currentUser { notifications }', () => { authorParams = { email: 'author@example.org', password: '1234', - id: 'author' + id: 'author', } await factory.create('User', authorParams) authorHeaders = await login(authorParams) @@ -56,7 +56,8 @@ describe('currentUser { notifications }', () => { describe('who mentions me in a post', () => { let post const title = 'Mentioning Al Capone' - const content = 'Hey @al-capone how do you do?' + const content = + 'Hey @al-capone how do you do?' beforeEach(async () => { const createPostMutation = ` @@ -74,20 +75,21 @@ describe('currentUser { notifications }', () => { }) it('sends you a notification', async () => { - const expectedContent = 'Hey @al-capone how do you do?' + const expectedContent = + 'Hey @al-capone how do you do?' const expected = { currentUser: { - notifications: [ - { read: false, post: { content: expectedContent } } - ] - } + notifications: [{ read: false, post: { content: expectedContent } }], + }, } await expect(client.request(query, { read: false })).resolves.toEqual(expected) }) describe('who mentions me again', () => { beforeEach(async () => { - const updatedContent = `${post.content} One more mention to @al-capone` + const updatedContent = `${ + post.content + } One more mention to @al-capone` // The response `post.content` contains a link but the XSSmiddleware // should have the `mention` CSS class removed. I discovered this // during development and thought: A feature not a bug! This way we @@ -106,14 +108,15 @@ describe('currentUser { notifications }', () => { }) it('creates exactly one more notification', async () => { - const expectedContent = 'Hey @al-capone how do you do? One more mention to @al-capone' + const expectedContent = + 'Hey @al-capone how do you do? One more mention to @al-capone' const expected = { currentUser: { notifications: [ { read: false, post: { content: expectedContent } }, - { read: false, post: { content: expectedContent } } - ] - } + { read: false, post: { content: expectedContent } }, + ], + }, } await expect(client.request(query, { read: false })).resolves.toEqual(expected) }) diff --git a/backend/src/middleware/orderByMiddleware.js b/backend/src/middleware/orderByMiddleware.js index 5f8aabb9e..64eac8b74 100644 --- a/backend/src/middleware/orderByMiddleware.js +++ b/backend/src/middleware/orderByMiddleware.js @@ -5,7 +5,7 @@ const defaultOrderBy = (resolve, root, args, context, resolveInfo) => { const newestFirst = { kind: 'Argument', name: { kind: 'Name', value: 'orderBy' }, - value: { kind: 'EnumValue', value: 'createdAt_desc' } + value: { kind: 'EnumValue', value: 'createdAt_desc' }, } const [fieldNode] = copy.fieldNodes if (fieldNode) fieldNode.arguments.push(newestFirst) @@ -14,6 +14,6 @@ const defaultOrderBy = (resolve, root, args, context, resolveInfo) => { export default { Query: { - Post: defaultOrderBy - } + Post: defaultOrderBy, + }, } diff --git a/backend/src/middleware/orderByMiddleware.spec.js b/backend/src/middleware/orderByMiddleware.spec.js index 2d85452e5..658447160 100644 --- a/backend/src/middleware/orderByMiddleware.spec.js +++ b/backend/src/middleware/orderByMiddleware.spec.js @@ -35,7 +35,7 @@ describe('Query', () => { { title: 'last' }, { title: 'third' }, { title: 'second' }, - { title: 'first' } + { title: 'first' }, ] const expected = { Post: posts } await expect(client.request(query)).resolves.toEqual(expected) @@ -51,7 +51,7 @@ describe('Query', () => { { title: 'first' }, { title: 'second' }, { title: 'third' }, - { title: 'last' } + { title: 'last' }, ] const expected = { Post: posts } await expect(client.request(query)).resolves.toEqual(expected) diff --git a/backend/src/middleware/passwordMiddleware.js b/backend/src/middleware/passwordMiddleware.js index 16272421a..1078e5529 100644 --- a/backend/src/middleware/passwordMiddleware.js +++ b/backend/src/middleware/passwordMiddleware.js @@ -8,7 +8,7 @@ export default { const result = await resolve(root, args, context, info) result.password = '*****' return result - } + }, }, Query: async (resolve, root, args, context, info) => { let result = await resolve(root, args, context, info) @@ -17,5 +17,5 @@ export default { return '*****' }) return result - } + }, } diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 85c584407..2e7a2cd1a 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -13,7 +13,7 @@ const isModerator = rule()(async (parent, args, { user }, info) => { }) const isAdmin = rule()(async (parent, args, { user }, info) => { - return user && (user.role === 'admin') + return user && user.role === 'admin' }) const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, context, info) => { @@ -21,14 +21,20 @@ const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, context, info) }) const belongsToMe = rule({ cache: 'no_cache' })(async (_, args, context) => { - const { driver, user: { id: userId } } = context + const { + driver, + user: { id: userId }, + } = context const { id: notificationId } = args const session = driver.session() - const result = await session.run(` + const result = await session.run( + ` MATCH (u:User {id: $userId})<-[:NOTIFIED]-(n:Notification {id: $notificationId}) RETURN n - `, { userId, notificationId }) - const [notification] = result.records.map((record) => { + `, + { userId, notificationId }, + ) + const [notification] = result.records.map(record => { return record.get('n') }) session.close() @@ -44,14 +50,19 @@ const isAuthor = rule({ cache: 'no_cache' })(async (parent, args, { user, driver if (!user) return false const session = driver.session() const { id: postId } = args - const result = await session.run(` + const result = await session.run( + ` MATCH (post:Post {id: $postId})<-[:WROTE]-(author) RETURN author - `, { postId }) - const [author] = result.records.map((record) => { + `, + { postId }, + ) + const [author] = result.records.map(record => { return record.get('author') }) - const { properties: { id: authorId } } = author + const { + properties: { id: authorId }, + } = author session.close() return authorId === user.id }) @@ -62,7 +73,7 @@ const permissions = shield({ Notification: isAdmin, statistics: allow, currentUser: allow, - Post: or(onlyEnabledContent, isModerator) + Post: or(onlyEnabledContent, isModerator), }, Mutation: { UpdateNotification: belongsToMe, @@ -88,14 +99,14 @@ const permissions = shield({ changePassword: isAuthenticated, enable: isModerator, disable: isModerator, - CreateComment: isAuthenticated + CreateComment: isAuthenticated, // CreateUser: allow, }, User: { email: isMyOwn, password: isMyOwn, - privateKey: isMyOwn - } + privateKey: isMyOwn, + }, }) export default permissions diff --git a/backend/src/middleware/permissionsMiddleware.spec.js b/backend/src/middleware/permissionsMiddleware.spec.js index e3c4beb00..fc1815631 100644 --- a/backend/src/middleware/permissionsMiddleware.spec.js +++ b/backend/src/middleware/permissionsMiddleware.spec.js @@ -10,12 +10,12 @@ describe('authorization', () => { await factory.create('User', { email: 'owner@example.org', name: 'Owner', - password: 'iamtheowner' + password: 'iamtheowner', }) await factory.create('User', { email: 'someone@example.org', name: 'Someone else', - password: 'else' + password: 'else', }) }) @@ -39,14 +39,14 @@ describe('authorization', () => { await expect(action()).rejects.toThrow('Not Authorised!') }) - it('does not expose the owner\'s email address', async () => { + it("does not expose the owner's email address", async () => { let response = {} try { await action() } catch (error) { response = error.response.data } finally { - expect(response).toEqual({ User: [ null ] }) + expect(response).toEqual({ User: [null] }) } }) }) @@ -55,12 +55,12 @@ describe('authorization', () => { beforeEach(() => { loginCredentials = { email: 'owner@example.org', - password: 'iamtheowner' + password: 'iamtheowner', } }) - it('exposes the owner\'s email address', async () => { - await expect(action()).resolves.toEqual({ User: [ { email: 'owner@example.org' } ] }) + it("exposes the owner's email address", async () => { + await expect(action()).resolves.toEqual({ User: [{ email: 'owner@example.org' }] }) }) }) @@ -68,7 +68,7 @@ describe('authorization', () => { beforeEach(async () => { loginCredentials = { email: 'someone@example.org', - password: 'else' + password: 'else', } }) @@ -76,14 +76,14 @@ describe('authorization', () => { await expect(action()).rejects.toThrow('Not Authorised!') }) - it('does not expose the owner\'s email address', async () => { + it("does not expose the owner's email address", async () => { let response try { await action() } catch (error) { response = error.response.data } - expect(response).toEqual({ User: [ null ] }) + expect(response).toEqual({ User: [null] }) }) }) }) diff --git a/backend/src/middleware/sluggifyMiddleware.js b/backend/src/middleware/sluggifyMiddleware.js index c94feb55e..2b1f25d5c 100644 --- a/backend/src/middleware/sluggifyMiddleware.js +++ b/backend/src/middleware/sluggifyMiddleware.js @@ -3,12 +3,9 @@ import uniqueSlug from './slugify/uniqueSlug' const isUniqueFor = (context, type) => { return async slug => { const session = context.driver.session() - const response = await session.run( - `MATCH(p:${type} {slug: $slug }) return p.slug`, - { - slug - } - ) + const response = await session.run(`MATCH(p:${type} {slug: $slug }) return p.slug`, { + slug, + }) session.close() return response.records.length === 0 } @@ -17,28 +14,20 @@ const isUniqueFor = (context, type) => { export default { Mutation: { CreatePost: async (resolve, root, args, context, info) => { - args.slug = - args.slug || - (await uniqueSlug(args.title, isUniqueFor(context, 'Post'))) + args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post'))) return resolve(root, args, context, info) }, CreateUser: async (resolve, root, args, context, info) => { - args.slug = - args.slug || - (await uniqueSlug(args.name, isUniqueFor(context, 'User'))) + args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'User'))) return resolve(root, args, context, info) }, CreateOrganization: async (resolve, root, args, context, info) => { - args.slug = - args.slug || - (await uniqueSlug(args.name, isUniqueFor(context, 'Organization'))) + args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Organization'))) return resolve(root, args, context, info) }, CreateCategory: async (resolve, root, args, context, info) => { - args.slug = - args.slug || - (await uniqueSlug(args.name, isUniqueFor(context, 'Category'))) + args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Category'))) return resolve(root, args, context, info) - } - } + }, + }, } diff --git a/backend/src/middleware/slugify/uniqueSlug.js b/backend/src/middleware/slugify/uniqueSlug.js index 64e38c8ae..69aef2d1b 100644 --- a/backend/src/middleware/slugify/uniqueSlug.js +++ b/backend/src/middleware/slugify/uniqueSlug.js @@ -1,7 +1,7 @@ import slugify from 'slug' -export default async function uniqueSlug (string, isUnique) { +export default async function uniqueSlug(string, isUnique) { let slug = slugify(string || 'anonymous', { - lower: true + lower: true, }) if (await isUnique(slug)) return slug @@ -10,6 +10,6 @@ export default async function uniqueSlug (string, isUnique) { do { count += 1 uniqueSlug = `${slug}-${count}` - } while (!await isUnique(uniqueSlug)) + } while (!(await isUnique(uniqueSlug))) return uniqueSlug } diff --git a/backend/src/middleware/slugify/uniqueSlug.spec.js b/backend/src/middleware/slugify/uniqueSlug.spec.js index 6772a20c2..e34af86a1 100644 --- a/backend/src/middleware/slugify/uniqueSlug.spec.js +++ b/backend/src/middleware/slugify/uniqueSlug.spec.js @@ -3,14 +3,14 @@ import uniqueSlug from './uniqueSlug' describe('uniqueSlug', () => { it('slugifies given string', () => { const string = 'Hello World' - const isUnique = jest.fn() - .mockResolvedValue(true) + const isUnique = jest.fn().mockResolvedValue(true) expect(uniqueSlug(string, isUnique)).resolves.toEqual('hello-world') }) it('increments slugified string until unique', () => { const string = 'Hello World' - const isUnique = jest.fn() + const isUnique = jest + .fn() .mockResolvedValueOnce(false) .mockResolvedValueOnce(true) expect(uniqueSlug(string, isUnique)).resolves.toEqual('hello-world-1') @@ -18,8 +18,7 @@ describe('uniqueSlug', () => { it('slugify null string', () => { const string = null - const isUnique = jest.fn() - .mockResolvedValue(true) + const isUnique = jest.fn().mockResolvedValue(true) expect(uniqueSlug(string, isUnique)).resolves.toEqual('anonymous') }) }) diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 6e667056c..7ca4ec193 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -10,7 +10,7 @@ beforeEach(async () => { await factory.create('User', { email: 'user@example.org', password: '1234' }) await factory.create('User', { email: 'someone@example.org', - password: '1234' + password: '1234', }) headers = await login({ email: 'user@example.org', password: '1234' }) authenticatedClient = new GraphQLClient(host, { headers }) @@ -30,7 +30,7 @@ describe('slugify', () => { ) { slug } }`) expect(response).toEqual({ - CreatePost: { slug: 'i-am-a-brand-new-post' } + CreatePost: { slug: 'i-am-a-brand-new-post' }, }) }) @@ -38,11 +38,11 @@ describe('slugify', () => { beforeEach(async () => { const asSomeoneElse = await Factory().authenticateAs({ email: 'someone@example.org', - password: '1234' + password: '1234', }) await asSomeoneElse.create('Post', { title: 'Pre-existing post', - slug: 'pre-existing-post' + slug: 'pre-existing-post', }) }) @@ -54,7 +54,7 @@ describe('slugify', () => { ) { slug } }`) expect(response).toEqual({ - CreatePost: { slug: 'pre-existing-post-1' } + CreatePost: { slug: 'pre-existing-post-1' }, }) }) @@ -67,7 +67,7 @@ describe('slugify', () => { content: "Some content", slug: "pre-existing-post" ) { slug } - }`) + }`), ).rejects.toThrow('already exists') }) }) @@ -81,32 +81,26 @@ describe('slugify', () => { }`) } it('generates a slug based on name', async () => { - await expect( - action('CreateUser', 'name: "I am a user"') - ).resolves.toEqual({ CreateUser: { slug: 'i-am-a-user' } }) + await expect(action('CreateUser', 'name: "I am a user"')).resolves.toEqual({ + CreateUser: { slug: 'i-am-a-user' }, + }) }) describe('if slug exists', () => { beforeEach(async () => { - await action( - 'CreateUser', - 'name: "Pre-existing user", slug: "pre-existing-user"' - ) + await action('CreateUser', 'name: "Pre-existing user", slug: "pre-existing-user"') }) it('chooses another slug', async () => { - await expect( - action('CreateUser', 'name: "pre-existing-user"') - ).resolves.toEqual({ CreateUser: { slug: 'pre-existing-user-1' } }) + await expect(action('CreateUser', 'name: "pre-existing-user"')).resolves.toEqual({ + CreateUser: { slug: 'pre-existing-user-1' }, + }) }) describe('but if the client specifies a slug', () => { it('rejects CreateUser', async () => { await expect( - action( - 'CreateUser', - 'name: "Pre-existing user", slug: "pre-existing-user"' - ) + action('CreateUser', 'name: "Pre-existing user", slug: "pre-existing-user"'), ).rejects.toThrow('already exists') }) }) diff --git a/backend/src/middleware/softDeleteMiddleware.js b/backend/src/middleware/softDeleteMiddleware.js index 53beca219..cc5aa06c5 100644 --- a/backend/src/middleware/softDeleteMiddleware.js +++ b/backend/src/middleware/softDeleteMiddleware.js @@ -30,7 +30,7 @@ export default { Query: { Post: setDefaultFilters, Comment: setDefaultFilters, - User: setDefaultFilters + User: setDefaultFilters, }, Mutation: async (resolve, root, args, context, info) => { args.disabled = false @@ -42,5 +42,5 @@ export default { }, Post: obfuscateDisabled, User: obfuscateDisabled, - Comment: obfuscateDisabled + Comment: obfuscateDisabled, } diff --git a/backend/src/middleware/softDeleteMiddleware.spec.js b/backend/src/middleware/softDeleteMiddleware.spec.js index f007888ed..4265599dd 100644 --- a/backend/src/middleware/softDeleteMiddleware.spec.js +++ b/backend/src/middleware/softDeleteMiddleware.spec.js @@ -11,32 +11,51 @@ beforeAll(async () => { // For performance reasons we do this only once await Promise.all([ factory.create('User', { id: 'u1', role: 'user', email: 'user@example.org', password: '1234' }), - factory.create('User', { id: 'm1', role: 'moderator', email: 'moderator@example.org', password: '1234' }), - factory.create('User', { id: 'u2', role: 'user', name: 'Offensive Name', avatar: '/some/offensive/avatar.jpg', about: 'This self description is very offensive', email: 'troll@example.org', password: '1234' }) + factory.create('User', { + id: 'm1', + role: 'moderator', + email: 'moderator@example.org', + password: '1234', + }), + factory.create('User', { + id: 'u2', + role: 'user', + name: 'Offensive Name', + avatar: '/some/offensive/avatar.jpg', + about: 'This self description is very offensive', + email: 'troll@example.org', + password: '1234', + }), ]) await factory.authenticateAs({ email: 'user@example.org', password: '1234' }) await Promise.all([ factory.follow({ id: 'u2', type: 'User' }), factory.create('Post', { id: 'p1', title: 'Deleted post', deleted: true }), - factory.create('Post', { id: 'p3', title: 'Publicly visible post', deleted: false }) + factory.create('Post', { id: 'p3', title: 'Publicly visible post', deleted: false }), ]) await Promise.all([ - factory.create('Comment', { id: 'c2', postId: 'p3', content: 'Enabled comment on public post' }) + factory.create('Comment', { + id: 'c2', + postId: 'p3', + content: 'Enabled comment on public post', + }), ]) - await Promise.all([ - factory.relate('Comment', 'Author', { from: 'u1', to: 'c2' }) - ]) + await Promise.all([factory.relate('Comment', 'Author', { from: 'u1', to: 'c2' })]) const asTroll = Factory() await asTroll.authenticateAs({ email: 'troll@example.org', password: '1234' }) - await asTroll.create('Post', { id: 'p2', title: 'Disabled post', content: 'This is an offensive post content', image: '/some/offensive/image.jpg', deleted: false }) + await asTroll.create('Post', { + id: 'p2', + title: 'Disabled post', + content: 'This is an offensive post content', + image: '/some/offensive/image.jpg', + deleted: false, + }) await asTroll.create('Comment', { id: 'c1', postId: 'p3', content: 'Disabled comment' }) - await Promise.all([ - asTroll.relate('Comment', 'Author', { from: 'u2', to: 'c1' }) - ]) + await Promise.all([asTroll.relate('Comment', 'Author', { from: 'u2', to: 'c1' })]) const asModerator = Factory() await asModerator.authenticateAs({ email: 'moderator@example.org', password: '1234' }) @@ -65,7 +84,8 @@ describe('softDeleteMiddleware', () => { user = response.User[0].following[0] } const beforePost = async () => { - query = '{ User(id: "u1") { following { contributions { title image content contentExcerpt } } } }' + query = + '{ User(id: "u1") { following { contributions { title image content contentExcerpt } } } }' const response = await action() post = response.User[0].following[0].contributions[0] } @@ -84,7 +104,8 @@ describe('softDeleteMiddleware', () => { beforeEach(beforeUser) it('displays name', () => expect(user.name).toEqual('Offensive Name')) - it('displays about', () => expect(user.about).toEqual('This self description is very offensive')) + it('displays about', () => + expect(user.about).toEqual('This self description is very offensive')) it('displays avatar', () => expect(user.avatar).toEqual('/some/offensive/avatar.jpg')) }) @@ -92,8 +113,10 @@ describe('softDeleteMiddleware', () => { beforeEach(beforePost) it('displays title', () => expect(post.title).toEqual('Disabled post')) - it('displays content', () => expect(post.content).toEqual('This is an offensive post content')) - it('displays contentExcerpt', () => expect(post.contentExcerpt).toEqual('This is an offensive post content')) + it('displays content', () => + expect(post.content).toEqual('This is an offensive post content')) + it('displays contentExcerpt', () => + expect(post.contentExcerpt).toEqual('This is an offensive post content')) it('displays image', () => expect(post.image).toEqual('/some/offensive/image.jpg')) }) @@ -101,7 +124,8 @@ describe('softDeleteMiddleware', () => { beforeEach(beforeComment) it('displays content', () => expect(comment.content).toEqual('Disabled comment')) - it('displays contentExcerpt', () => expect(comment.contentExcerpt).toEqual('Disabled comment')) + it('displays contentExcerpt', () => + expect(comment.contentExcerpt).toEqual('Disabled comment')) }) }) @@ -162,10 +186,7 @@ describe('softDeleteMiddleware', () => { }) it('shows disabled but hides deleted posts', async () => { - const expected = [ - { title: 'Disabled post' }, - { title: 'Publicly visible post' } - ] + const expected = [{ title: 'Disabled post' }, { title: 'Publicly visible post' }] const { Post } = await action() await expect(Post).toEqual(expect.arrayContaining(expected)) }) @@ -185,9 +206,11 @@ describe('softDeleteMiddleware', () => { it('conceals disabled comments', async () => { const expected = [ { content: 'Enabled comment on public post' }, - { content: 'UNAVAILABLE' } + { content: 'UNAVAILABLE' }, ] - const { Post: [{ comments }] } = await action() + const { + Post: [{ comments }], + } = await action() await expect(comments).toEqual(expect.arrayContaining(expected)) }) }) @@ -201,9 +224,11 @@ describe('softDeleteMiddleware', () => { it('shows disabled comments', async () => { const expected = [ { content: 'Enabled comment on public post' }, - { content: 'Disabled comment' } + { content: 'Disabled comment' }, ] - const { Post: [{ comments }] } = await action() + const { + Post: [{ comments }], + } = await action() await expect(comments).toEqual(expect.arrayContaining(expected)) }) }) diff --git a/backend/src/middleware/userMiddleware.js b/backend/src/middleware/userMiddleware.js index b3fc1bf2c..079ba310a 100644 --- a/backend/src/middleware/userMiddleware.js +++ b/backend/src/middleware/userMiddleware.js @@ -15,6 +15,6 @@ export default { const result = await resolve(root, args, context, info) await createOrUpdateLocations(args.id, args.locationName, context.driver) return result - } - } + }, + }, } diff --git a/backend/src/middleware/validation/index.js b/backend/src/middleware/validation/index.js index de9be72e9..8e9be59ef 100644 --- a/backend/src/middleware/validation/index.js +++ b/backend/src/middleware/validation/index.js @@ -26,6 +26,6 @@ export default { Mutation: { CreateUser: validateUsername, UpdateUser: validateUsername, - CreateSocialMedia: validateUrl - } + CreateSocialMedia: validateUrl, + }, } diff --git a/backend/src/middleware/xssMiddleware.js b/backend/src/middleware/xssMiddleware.js index 2607f4210..06aa5b306 100644 --- a/backend/src/middleware/xssMiddleware.js +++ b/backend/src/middleware/xssMiddleware.js @@ -5,7 +5,7 @@ import sanitizeHtml from 'sanitize-html' import cheerio from 'cheerio' import linkifyHtml from 'linkifyjs/html' -const embedToAnchor = (content) => { +const embedToAnchor = content => { const $ = cheerio.load(content) $('div[data-url-embed]').each((i, el) => { let url = el.attribs['data-url-embed'] @@ -15,7 +15,7 @@ const embedToAnchor = (content) => { return $('body').html() } -function clean (dirty) { +function clean(dirty) { if (!dirty) { return dirty } @@ -24,27 +24,48 @@ function clean (dirty) { dirty = embedToAnchor(dirty) dirty = linkifyHtml(dirty) dirty = sanitizeHtml(dirty, { - allowedTags: ['iframe', 'img', 'p', 'h3', 'h4', 'br', 'hr', 'b', 'i', 'em', 'strong', 'a', 'pre', 'ul', 'li', 'ol', 's', 'strike', 'span', 'blockquote'], + allowedTags: [ + 'iframe', + 'img', + 'p', + 'h3', + 'h4', + 'br', + 'hr', + 'b', + 'i', + 'em', + 'strong', + 'a', + 'pre', + 'ul', + 'li', + 'ol', + 's', + 'strike', + 'span', + 'blockquote', + ], allowedAttributes: { a: ['href', 'class', 'target', 'data-*', 'contenteditable'], span: ['contenteditable', 'class', 'data-*'], img: ['src'], - iframe: ['src', 'class', 'frameborder', 'allowfullscreen'] + iframe: ['src', 'class', 'frameborder', 'allowfullscreen'], }, allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com'], parser: { - lowerCaseTags: true + lowerCaseTags: true, }, transformTags: { - iframe: function (tagName, attribs) { + iframe: function(tagName, attribs) { return { tagName: 'a', text: attribs.src, attribs: { href: attribs.src, target: '_blank', - 'data-url-embed': '' - } + 'data-url-embed': '', + }, } }, h1: 'h3', @@ -53,19 +74,19 @@ function clean (dirty) { h4: 'h4', h5: 'strong', i: 'em', - a: function (tagName, attribs) { + a: function(tagName, attribs) { return { tagName: 'a', attribs: { href: attribs.href, target: '_blank', - rel: 'noopener noreferrer nofollow' - } + rel: 'noopener noreferrer nofollow', + }, } }, b: 'strong', s: 'strike', - img: function (tagName, attribs) { + img: function(tagName, attribs) { let src = attribs.src if (!src) { @@ -88,11 +109,11 @@ function clean (dirty) { tagName: 'img', attribs: { // TODO: use environment variables - src: `http://localhost:3050/images?url=${src}` - } + src: `http://localhost:3050/images?url=${src}`, + }, } - } - } + }, + }, }) // remove empty html tags and duplicated linebreaks and returns @@ -100,10 +121,7 @@ function clean (dirty) { // remove all tags with "space only" .replace(/<[a-z-]+>[\s]+<\/[a-z-]+>/gim, '') // remove all iframes - .replace( - /(]*)(>)[^>]*\/*>/gim, - '' - ) + .replace(/(]*)(>)[^>]*\/*>/gim, '') .replace(/[\n]{3,}/gim, '\n\n') .replace(/(\r\n|\n\r|\r|\n)/g, '
$1') @@ -111,15 +129,9 @@ function clean (dirty) { // limit linebreaks to max 2 (equivalent to html "br" linebreak) .replace(/(
\s*){2,}/gim, '
') // remove additional linebreaks after p tags - .replace( - /<\/(p|div|th|tr)>\s*(
\s*)+\s*<(p|div|th|tr)>/gim, - '

' - ) + .replace(/<\/(p|div|th|tr)>\s*(
\s*)+\s*<(p|div|th|tr)>/gim, '

') // remove additional linebreaks inside p tags - .replace( - /<[a-z-]+>(<[a-z-]+>)*\s*(
\s*)+\s*(<\/[a-z-]+>)*<\/[a-z-]+>/gim, - '' - ) + .replace(/<[a-z-]+>(<[a-z-]+>)*\s*(
\s*)+\s*(<\/[a-z-]+>)*<\/[a-z-]+>/gim, '') // remove additional linebreaks when first child inside p tags .replace(/

(\s*
\s*)+/gim, '

') // remove additional linebreaks when last child inside p tags @@ -138,5 +150,5 @@ export default { Query: async (resolve, root, args, context, info) => { const result = await resolve(root, args, context, info) return walkRecursive(result, fields, clean) - } + }, } diff --git a/backend/src/mocks/index.js b/backend/src/mocks/index.js index a87ccfbbc..7b453c8c6 100644 --- a/backend/src/mocks/index.js +++ b/backend/src/mocks/index.js @@ -1,15 +1,14 @@ - import faker from 'faker' export default { User: () => ({ name: () => `${faker.name.firstName()} ${faker.name.lastName()}`, - email: () => `${faker.internet.email()}` + email: () => `${faker.internet.email()}`, }), Post: () => ({ title: () => faker.lorem.lines(1), slug: () => faker.lorem.slug(3), content: () => faker.lorem.paragraphs(5), - contentExcerpt: () => faker.lorem.paragraphs(1) - }) + contentExcerpt: () => faker.lorem.paragraphs(1), + }), } diff --git a/backend/src/resolvers/badges.spec.js b/backend/src/resolvers/badges.spec.js index 1966ce241..f5e15f4a2 100644 --- a/backend/src/resolvers/badges.spec.js +++ b/backend/src/resolvers/badges.spec.js @@ -10,17 +10,17 @@ describe('badges', () => { await factory.create('User', { email: 'user@example.org', role: 'user', - password: '1234' + password: '1234', }) await factory.create('User', { id: 'u2', role: 'moderator', - email: 'moderator@example.org' + email: 'moderator@example.org', }) await factory.create('User', { id: 'u3', role: 'admin', - email: 'admin@example.org' + email: 'admin@example.org', }) }) @@ -34,7 +34,7 @@ describe('badges', () => { key: 'indiegogo_en_racoon', type: 'crowdfunding', status: 'permanent', - icon: '/img/badges/indiegogo_en_racoon.svg' + icon: '/img/badges/indiegogo_en_racoon.svg', } const mutation = ` @@ -58,9 +58,7 @@ describe('badges', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -76,8 +74,8 @@ describe('badges', () => { id: 'b1', key: 'indiegogo_en_racoon', status: 'permanent', - type: 'crowdfunding' - } + type: 'crowdfunding', + }, } await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) @@ -90,9 +88,7 @@ describe('badges', () => { }) it('throws authorization error', async () => { - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) }) @@ -104,7 +100,7 @@ describe('badges', () => { }) const variables = { id: 'b1', - key: 'whatever' + key: 'whatever', } const mutation = ` @@ -119,9 +115,7 @@ describe('badges', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -132,9 +126,7 @@ describe('badges', () => { }) it('throws authorization error', async () => { - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -147,8 +139,8 @@ describe('badges', () => { const expected = { UpdateBadge: { id: 'b1', - key: 'whatever' - } + key: 'whatever', + }, } await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) @@ -161,7 +153,7 @@ describe('badges', () => { await factory.create('Badge', { id: 'b1' }) }) const variables = { - id: 'b1' + id: 'b1', } const mutation = ` @@ -175,9 +167,7 @@ describe('badges', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -188,9 +178,7 @@ describe('badges', () => { }) it('throws authorization error', async () => { - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -202,8 +190,8 @@ describe('badges', () => { it('deletes a badge', async () => { const expected = { DeleteBadge: { - id: 'b1' - } + id: 'b1', + }, } await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index d4775b235..949b77041 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -23,11 +23,13 @@ export default { } const session = context.driver.session() - const postQueryRes = await session.run(` + const postQueryRes = await session.run( + ` MATCH (post:Post {id: $postId}) - RETURN post`, { - postId - } + RETURN post`, + { + postId, + }, ) const [post] = postQueryRes.records.map(record => { return record.get('post') @@ -38,18 +40,20 @@ export default { } const comment = await neo4jgraphql(object, params, context, resolveInfo, false) - await session.run(` + await session.run( + ` MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}), (author:User {id: $userId}) MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author) - RETURN post`, { - userId: context.user.id, - postId, - commentId: comment.id - } + RETURN post`, + { + userId: context.user.id, + postId, + commentId: comment.id, + }, ) session.close() return comment - } - } + }, + }, } diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js index 87a0df270..451c559f1 100644 --- a/backend/src/resolvers/comments.spec.js +++ b/backend/src/resolvers/comments.spec.js @@ -12,7 +12,7 @@ let createCommentVariablesWithNonExistentPost beforeEach(async () => { await factory.create('User', { email: 'test@example.org', - password: '1234' + password: '1234', }) }) @@ -47,10 +47,12 @@ describe('CreateComment', () => { it('throws authorization error', async () => { createCommentVariables = { postId: 'p1', - content: 'I\'m not authorised to comment' + content: "I'm not authorised to comment", } client = new GraphQLClient(host) - await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow('Not Authorised') + await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow( + 'Not Authorised', + ) }) }) @@ -61,12 +63,12 @@ describe('CreateComment', () => { client = new GraphQLClient(host, { headers }) createCommentVariables = { postId: 'p1', - content: 'I\'m authorised to comment' + content: "I'm authorised to comment", } createPostVariables = { id: 'p1', title: 'post to comment on', - content: 'please comment on me' + content: 'please comment on me', } await client.request(createPostMutation, createPostVariables) }) @@ -74,11 +76,13 @@ describe('CreateComment', () => { it('creates a comment', async () => { const expected = { CreateComment: { - content: 'I\'m authorised to comment' - } + content: "I'm authorised to comment", + }, } - await expect(client.request(createCommentMutation, createCommentVariables)).resolves.toMatchObject(expected) + await expect( + client.request(createCommentMutation, createCommentVariables), + ).resolves.toMatchObject(expected) }) it('assigns the authenticated user as author', async () => { @@ -92,86 +96,96 @@ describe('CreateComment', () => { } }`) - expect(User).toEqual([ { comments: [ { content: 'I\'m authorised to comment' } ] } ]) + expect(User).toEqual([{ comments: [{ content: "I'm authorised to comment" }] }]) }) it('throw an error if an empty string is sent from the editor as content', async () => { createCommentVariables = { postId: 'p1', - content: '

' + content: '

', } - await expect(client.request(createCommentMutation, createCommentVariables)) - .rejects.toThrow('Comment must be at least 1 character long!') + await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow( + 'Comment must be at least 1 character long!', + ) }) it('throws an error if a comment sent from the editor does not contain a single character', async () => { createCommentVariables = { postId: 'p1', - content: '

' + content: '

', } - await expect(client.request(createCommentMutation, createCommentVariables)) - .rejects.toThrow('Comment must be at least 1 character long!') + await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow( + 'Comment must be at least 1 character long!', + ) }) it('throws an error if postId is sent as an empty string', async () => { createCommentVariables = { postId: 'p1', - content: '' + content: '', } - await expect(client.request(createCommentMutation, createCommentVariables)) - .rejects.toThrow('Comment must be at least 1 character long!') + await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow( + 'Comment must be at least 1 character long!', + ) }) it('throws an error if content is sent as an string of empty characters', async () => { createCommentVariables = { postId: 'p1', - content: ' ' + content: ' ', } - await expect(client.request(createCommentMutation, createCommentVariables)) - .rejects.toThrow('Comment must be at least 1 character long!') + await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow( + 'Comment must be at least 1 character long!', + ) }) it('throws an error if postId is sent as an empty string', async () => { createCommentVariablesSansPostId = { postId: '', - content: 'this comment should not be created' + content: 'this comment should not be created', } - await expect(client.request(createCommentMutation, createCommentVariablesSansPostId)) - .rejects.toThrow('Comment cannot be created without a post!') + await expect( + client.request(createCommentMutation, createCommentVariablesSansPostId), + ).rejects.toThrow('Comment cannot be created without a post!') }) it('throws an error if postId is sent as an string of empty characters', async () => { createCommentVariablesSansPostId = { postId: ' ', - content: 'this comment should not be created' + content: 'this comment should not be created', } - await expect(client.request(createCommentMutation, createCommentVariablesSansPostId)) - .rejects.toThrow('Comment cannot be created without a post!') + await expect( + client.request(createCommentMutation, createCommentVariablesSansPostId), + ).rejects.toThrow('Comment cannot be created without a post!') }) it('throws an error if the post does not exist in the database', async () => { createCommentVariablesWithNonExistentPost = { postId: 'p2', - content: 'comment should not be created cause the post doesn\'t exist' + content: "comment should not be created cause the post doesn't exist", } - await expect(client.request(createCommentMutation, createCommentVariablesWithNonExistentPost)) - .rejects.toThrow('Comment cannot be created without a post!') + await expect( + client.request(createCommentMutation, createCommentVariablesWithNonExistentPost), + ).rejects.toThrow('Comment cannot be created without a post!') }) it('does not create the comment with the postId as an attribute', async () => { const commentQueryVariablesByContent = { - content: 'I\'m authorised to comment' + content: "I'm authorised to comment", } await client.request(createCommentMutation, createCommentVariables) - const { Comment } = await client.request(commentQueryForPostId, commentQueryVariablesByContent) + const { Comment } = await client.request( + commentQueryForPostId, + commentQueryVariablesByContent, + ) expect(Comment).toEqual([{ postId: null }]) }) }) diff --git a/backend/src/resolvers/follow.js b/backend/src/resolvers/follow.js index df7b58891..4e9a3b27d 100644 --- a/backend/src/resolvers/follow.js +++ b/backend/src/resolvers/follow.js @@ -12,8 +12,8 @@ export default { { id, type, - userId: context.user.id - } + userId: context.user.id, + }, ) const [isFollowed] = transactionRes.records.map(record => { @@ -37,8 +37,8 @@ export default { { id, type, - userId: context.user.id - } + userId: context.user.id, + }, ) const [isFollowed] = transactionRes.records.map(record => { return record.get('isFollowed') @@ -46,6 +46,6 @@ export default { session.close() return isFollowed - } - } + }, + }, } diff --git a/backend/src/resolvers/follow.spec.js b/backend/src/resolvers/follow.spec.js index 081e49081..4a361b03d 100644 --- a/backend/src/resolvers/follow.spec.js +++ b/backend/src/resolvers/follow.spec.js @@ -6,12 +6,12 @@ const factory = Factory() let clientUser1 let headersUser1 -const mutationFollowUser = (id) => ` +const mutationFollowUser = id => ` mutation { follow(id: "${id}", type: User) } ` -const mutationUnfollowUser = (id) => ` +const mutationUnfollowUser = id => ` mutation { unfollow(id: "${id}", type: User) } @@ -21,12 +21,12 @@ beforeEach(async () => { await factory.create('User', { id: 'u1', email: 'test@example.org', - password: '1234' + password: '1234', }) await factory.create('User', { id: 'u2', email: 'test2@example.org', - password: '1234' + password: '1234', }) headersUser1 = await login({ email: 'test@example.org', password: '1234' }) @@ -43,18 +43,14 @@ describe('follow', () => { it('throws authorization error', async () => { let client client = new GraphQLClient(host) - await expect( - client.request(mutationFollowUser('u2')) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutationFollowUser('u2'))).rejects.toThrow('Not Authorised') }) }) it('I can follow another user', async () => { - const res = await clientUser1.request( - mutationFollowUser('u2') - ) + const res = await clientUser1.request(mutationFollowUser('u2')) const expected = { - follow: true + follow: true, } expect(res).toMatchObject(expected) @@ -65,20 +61,16 @@ describe('follow', () => { } }`) const expected2 = { - followedBy: [ - { id: 'u1' } - ], - followedByCurrentUser: true + followedBy: [{ id: 'u1' }], + followedByCurrentUser: true, } expect(User[0]).toMatchObject(expected2) }) it('I can`t follow myself', async () => { - const res = await clientUser1.request( - mutationFollowUser('u1') - ) + const res = await clientUser1.request(mutationFollowUser('u1')) const expected = { - follow: false + follow: false, } expect(res).toMatchObject(expected) @@ -90,7 +82,7 @@ describe('follow', () => { }`) const expected2 = { followedBy: [], - followedByCurrentUser: false + followedByCurrentUser: false, } expect(User[0]).toMatchObject(expected2) }) @@ -99,26 +91,20 @@ describe('follow', () => { describe('unauthenticated follow', () => { it('throws authorization error', async () => { // follow - await clientUser1.request( - mutationFollowUser('u2') - ) + await clientUser1.request(mutationFollowUser('u2')) // unfollow let client client = new GraphQLClient(host) - await expect( - client.request(mutationUnfollowUser('u2')) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutationUnfollowUser('u2'))).rejects.toThrow('Not Authorised') }) }) it('I can unfollow a user', async () => { // follow - await clientUser1.request( - mutationFollowUser('u2') - ) + await clientUser1.request(mutationFollowUser('u2')) // unfollow const expected = { - unfollow: true + unfollow: true, } const res = await clientUser1.request(mutationUnfollowUser('u2')) expect(res).toMatchObject(expected) @@ -131,7 +117,7 @@ describe('follow', () => { }`) const expected2 = { followedBy: [], - followedByCurrentUser: false + followedByCurrentUser: false, } expect(User[0]).toMatchObject(expected2) }) diff --git a/backend/src/resolvers/moderation.js b/backend/src/resolvers/moderation.js index 7bc1227ff..d61df7545 100644 --- a/backend/src/resolvers/moderation.js +++ b/backend/src/resolvers/moderation.js @@ -14,7 +14,7 @@ export default { const session = driver.session() const res = await session.run(cypher, { id, userId }) session.close() - const [resource] = res.records.map((record) => { + const [resource] = res.records.map(record => { return record.get('resource') }) if (!resource) return null @@ -31,11 +31,11 @@ export default { const session = driver.session() const res = await session.run(cypher, { id }) session.close() - const [resource] = res.records.map((record) => { + const [resource] = res.records.map(record => { return record.get('resource') }) if (!resource) return null return resource.id - } - } + }, + }, } diff --git a/backend/src/resolvers/moderation.spec.js b/backend/src/resolvers/moderation.spec.js index 28f4dc322..835e9e535 100644 --- a/backend/src/resolvers/moderation.spec.js +++ b/backend/src/resolvers/moderation.spec.js @@ -5,7 +5,7 @@ import { host, login } from '../jest/helpers' const factory = Factory() let client -const setupAuthenticateClient = (params) => { +const setupAuthenticateClient = params => { const authenticateClient = async () => { await factory.create('User', params) const headers = await login(params) @@ -46,7 +46,7 @@ describe('disable', () => { beforeEach(() => { // our defaul set of variables variables = { - id: 'blabla' + id: 'blabla', } }) @@ -63,7 +63,7 @@ describe('disable', () => { beforeEach(() => { authenticateClient = setupAuthenticateClient({ email: 'user@example.org', - password: '1234' + password: '1234', }) }) @@ -78,19 +78,17 @@ describe('disable', () => { id: 'u7', email: 'moderator@example.org', password: '1234', - role: 'moderator' + role: 'moderator', }) }) describe('on something that is not a (Comment|Post|User) ', () => { beforeEach(async () => { variables = { - id: 't23' + id: 't23', } createResource = () => { - return Promise.all([ - factory.create('Tag', { id: 't23' }) - ]) + return Promise.all([factory.create('Tag', { id: 't23' })]) } }) @@ -104,21 +102,28 @@ describe('disable', () => { describe('on a comment', () => { beforeEach(async () => { variables = { - id: 'c47' + id: 'c47', } createPostVariables = { id: 'p3', title: 'post to comment on', - content: 'please comment on me' + content: 'please comment on me', } createCommentVariables = { id: 'c47', postId: 'p3', - content: 'this comment was created for this post' + content: 'this comment was created for this post', } createResource = async () => { - await factory.create('User', { id: 'u45', email: 'commenter@example.org', password: '1234' }) - const asAuthenticatedUser = await factory.authenticateAs({ email: 'commenter@example.org', password: '1234' }) + await factory.create('User', { + id: 'u45', + email: 'commenter@example.org', + password: '1234', + }) + const asAuthenticatedUser = await factory.authenticateAs({ + email: 'commenter@example.org', + password: '1234', + }) await asAuthenticatedUser.create('Post', createPostVariables) await asAuthenticatedUser.create('Comment', createCommentVariables) } @@ -135,41 +140,39 @@ describe('disable', () => { const expected = { Comment: [{ id: 'c47', disabledBy: { id: 'u7' } }] } await setup() - await expect(client.request( - '{ Comment { id, disabledBy { id } } }' - )).resolves.toEqual(before) + await expect(client.request('{ Comment { id, disabledBy { id } } }')).resolves.toEqual( + before, + ) await action() - await expect(client.request( - '{ Comment(disabled: true) { id, disabledBy { id } } }' - )).resolves.toEqual(expected) + await expect( + client.request('{ Comment(disabled: true) { id, disabledBy { id } } }'), + ).resolves.toEqual(expected) }) it('updates .disabled on comment', async () => { - const before = { Comment: [ { id: 'c47', disabled: false } ] } - const expected = { Comment: [ { id: 'c47', disabled: true } ] } + const before = { Comment: [{ id: 'c47', disabled: false }] } + const expected = { Comment: [{ id: 'c47', disabled: true }] } await setup() - await expect(client.request( - '{ Comment { id disabled } }' - )).resolves.toEqual(before) + await expect(client.request('{ Comment { id disabled } }')).resolves.toEqual(before) await action() - await expect(client.request( - '{ Comment(disabled: true) { id disabled } }' - )).resolves.toEqual(expected) + await expect( + client.request('{ Comment(disabled: true) { id disabled } }'), + ).resolves.toEqual(expected) }) }) describe('on a post', () => { beforeEach(async () => { variables = { - id: 'p9' + id: 'p9', } createResource = async () => { await factory.create('User', { email: 'author@example.org', password: '1234' }) await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) await factory.create('Post', { - id: 'p9' // that's the ID we will look for + id: 'p9', // that's the ID we will look for }) } }) @@ -185,27 +188,25 @@ describe('disable', () => { const expected = { Post: [{ id: 'p9', disabledBy: { id: 'u7' } }] } await setup() - await expect(client.request( - '{ Post { id, disabledBy { id } } }' - )).resolves.toEqual(before) + await expect(client.request('{ Post { id, disabledBy { id } } }')).resolves.toEqual( + before, + ) await action() - await expect(client.request( - '{ Post(disabled: true) { id, disabledBy { id } } }' - )).resolves.toEqual(expected) + await expect( + client.request('{ Post(disabled: true) { id, disabledBy { id } } }'), + ).resolves.toEqual(expected) }) it('updates .disabled on post', async () => { - const before = { Post: [ { id: 'p9', disabled: false } ] } - const expected = { Post: [ { id: 'p9', disabled: true } ] } + const before = { Post: [{ id: 'p9', disabled: false }] } + const expected = { Post: [{ id: 'p9', disabled: true }] } await setup() - await expect(client.request( - '{ Post { id disabled } }' - )).resolves.toEqual(before) + await expect(client.request('{ Post { id disabled } }')).resolves.toEqual(before) await action() - await expect(client.request( - '{ Post(disabled: true) { id disabled } }' - )).resolves.toEqual(expected) + await expect(client.request('{ Post(disabled: true) { id disabled } }')).resolves.toEqual( + expected, + ) }) }) }) @@ -227,7 +228,7 @@ describe('enable', () => { beforeEach(() => { // our defaul set of variables variables = { - id: 'blabla' + id: 'blabla', } }) @@ -240,7 +241,7 @@ describe('enable', () => { beforeEach(() => { authenticateClient = setupAuthenticateClient({ email: 'user@example.org', - password: '1234' + password: '1234', }) }) @@ -254,20 +255,18 @@ describe('enable', () => { authenticateClient = setupAuthenticateClient({ role: 'moderator', email: 'someUser@example.org', - password: '1234' + password: '1234', }) }) describe('on something that is not a (Comment|Post|User) ', () => { beforeEach(async () => { variables = { - id: 't23' + id: 't23', } createResource = () => { // we cannot create a :DISABLED relationship here - return Promise.all([ - factory.create('Tag', { id: 't23' }) - ]) + return Promise.all([factory.create('Tag', { id: 't23' })]) } }) @@ -281,21 +280,28 @@ describe('enable', () => { describe('on a comment', () => { beforeEach(async () => { variables = { - id: 'c456' + id: 'c456', } createPostVariables = { id: 'p9', title: 'post to comment on', - content: 'please comment on me' + content: 'please comment on me', } createCommentVariables = { id: 'c456', postId: 'p9', - content: 'this comment was created for this post' + content: 'this comment was created for this post', } createResource = async () => { - await factory.create('User', { id: 'u123', email: 'author@example.org', password: '1234' }) - const asAuthenticatedUser = await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) + await factory.create('User', { + id: 'u123', + email: 'author@example.org', + password: '1234', + }) + const asAuthenticatedUser = await factory.authenticateAs({ + email: 'author@example.org', + password: '1234', + }) await asAuthenticatedUser.create('Post', createPostVariables) await asAuthenticatedUser.create('Comment', createCommentVariables) @@ -319,41 +325,43 @@ describe('enable', () => { const expected = { Comment: [{ id: 'c456', disabledBy: null }] } await setup() - await expect(client.request( - '{ Comment(disabled: true) { id, disabledBy { id } } }' - )).resolves.toEqual(before) + await expect( + client.request('{ Comment(disabled: true) { id, disabledBy { id } } }'), + ).resolves.toEqual(before) await action() - await expect(client.request( - '{ Comment { id, disabledBy { id } } }' - )).resolves.toEqual(expected) + await expect(client.request('{ Comment { id, disabledBy { id } } }')).resolves.toEqual( + expected, + ) }) it('updates .disabled on post', async () => { - const before = { Comment: [ { id: 'c456', disabled: true } ] } - const expected = { Comment: [ { id: 'c456', disabled: false } ] } + const before = { Comment: [{ id: 'c456', disabled: true }] } + const expected = { Comment: [{ id: 'c456', disabled: false }] } await setup() - await expect(client.request( - '{ Comment(disabled: true) { id disabled } }' - )).resolves.toEqual(before) + await expect( + client.request('{ Comment(disabled: true) { id disabled } }'), + ).resolves.toEqual(before) await action() // this updates .disabled - await expect(client.request( - '{ Comment { id disabled } }' - )).resolves.toEqual(expected) + await expect(client.request('{ Comment { id disabled } }')).resolves.toEqual(expected) }) }) describe('on a post', () => { beforeEach(async () => { variables = { - id: 'p9' + id: 'p9', } createResource = async () => { - await factory.create('User', { id: 'u123', email: 'author@example.org', password: '1234' }) + await factory.create('User', { + id: 'u123', + email: 'author@example.org', + password: '1234', + }) await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) await factory.create('Post', { - id: 'p9' // that's the ID we will look for + id: 'p9', // that's the ID we will look for }) const disableMutation = ` @@ -376,27 +384,25 @@ describe('enable', () => { const expected = { Post: [{ id: 'p9', disabledBy: null }] } await setup() - await expect(client.request( - '{ Post(disabled: true) { id, disabledBy { id } } }' - )).resolves.toEqual(before) + await expect( + client.request('{ Post(disabled: true) { id, disabledBy { id } } }'), + ).resolves.toEqual(before) await action() - await expect(client.request( - '{ Post { id, disabledBy { id } } }' - )).resolves.toEqual(expected) + await expect(client.request('{ Post { id, disabledBy { id } } }')).resolves.toEqual( + expected, + ) }) it('updates .disabled on post', async () => { - const before = { Post: [ { id: 'p9', disabled: true } ] } - const expected = { Post: [ { id: 'p9', disabled: false } ] } + const before = { Post: [{ id: 'p9', disabled: true }] } + const expected = { Post: [{ id: 'p9', disabled: false }] } await setup() - await expect(client.request( - '{ Post(disabled: true) { id disabled } }' - )).resolves.toEqual(before) + await expect(client.request('{ Post(disabled: true) { id disabled } }')).resolves.toEqual( + before, + ) await action() // this updates .disabled - await expect(client.request( - '{ Post { id disabled } }' - )).resolves.toEqual(expected) + await expect(client.request('{ Post { id disabled } }')).resolves.toEqual(expected) }) }) }) diff --git a/backend/src/resolvers/notifications.js b/backend/src/resolvers/notifications.js index bc3da0acf..ddc1985cf 100644 --- a/backend/src/resolvers/notifications.js +++ b/backend/src/resolvers/notifications.js @@ -4,11 +4,11 @@ export default { Query: { Notification: (object, params, context, resolveInfo) => { return neo4jgraphql(object, params, context, resolveInfo, false) - } + }, }, Mutation: { UpdateNotification: (object, params, context, resolveInfo) => { return neo4jgraphql(object, params, context, resolveInfo, false) - } - } + }, + }, } diff --git a/backend/src/resolvers/notifications.spec.js b/backend/src/resolvers/notifications.spec.js index 799bc1594..37d8c83ff 100644 --- a/backend/src/resolvers/notifications.spec.js +++ b/backend/src/resolvers/notifications.spec.js @@ -1,4 +1,3 @@ - import Factory from '../seed/factories' import { GraphQLClient } from 'graphql-request' import { host, login } from '../jest/helpers' @@ -8,7 +7,7 @@ let client let userParams = { id: 'you', email: 'test@example.org', - password: '1234' + password: '1234', } beforeEach(async () => { @@ -49,12 +48,12 @@ describe('currentUser { notifications }', () => { const neighborParams = { email: 'neighbor@example.org', password: '1234', - id: 'neighbor' + id: 'neighbor', } await Promise.all([ factory.create('User', neighborParams), factory.create('Notification', { id: 'not-for-you' }), - factory.create('Notification', { id: 'already-seen', read: true }) + factory.create('Notification', { id: 'already-seen', read: true }), ]) await factory.create('Notification', { id: 'unseen' }) await factory.authenticateAs(neighborParams) @@ -65,7 +64,7 @@ describe('currentUser { notifications }', () => { factory.relate('Notification', 'User', { from: 'unseen', to: 'you' }), factory.relate('Notification', 'Post', { from: 'p1', to: 'unseen' }), factory.relate('Notification', 'User', { from: 'already-seen', to: 'you' }), - factory.relate('Notification', 'Post', { from: 'p1', to: 'already-seen' }) + factory.relate('Notification', 'Post', { from: 'p1', to: 'already-seen' }), ]) }) @@ -84,10 +83,8 @@ describe('currentUser { notifications }', () => { it('returns only unread notifications of current user', async () => { const expected = { currentUser: { - notifications: [ - { id: 'unseen', post: { id: 'p1' } } - ] - } + notifications: [{ id: 'unseen', post: { id: 'p1' } }], + }, } await expect(client.request(query, variables)).resolves.toEqual(expected) }) @@ -109,9 +106,9 @@ describe('currentUser { notifications }', () => { currentUser: { notifications: [ { id: 'unseen', post: { id: 'p1' } }, - { id: 'already-seen', post: { id: 'p1' } } - ] - } + { id: 'already-seen', post: { id: 'p1' } }, + ], + }, } await expect(client.request(query, variables)).resolves.toEqual(expected) }) @@ -136,7 +133,7 @@ describe('UpdateNotification', () => { id: 'mentioned-1', email: 'mentioned@example.org', password: '1234', - slug: 'mentioned' + slug: 'mentioned', } await factory.create('User', mentionedParams) await factory.create('Notification', { id: 'to-be-updated' }) @@ -144,7 +141,7 @@ describe('UpdateNotification', () => { await factory.create('Post', { id: 'p1' }) await Promise.all([ factory.relate('Notification', 'User', { from: 'to-be-updated', to: 'mentioned-1' }), - factory.relate('Notification', 'Post', { from: 'p1', to: 'to-be-updated' }) + factory.relate('Notification', 'Post', { from: 'p1', to: 'to-be-updated' }), ]) }) diff --git a/backend/src/resolvers/posts.js b/backend/src/resolvers/posts.js index 5b06c38fa..e4d0d6876 100644 --- a/backend/src/resolvers/posts.js +++ b/backend/src/resolvers/posts.js @@ -8,15 +8,16 @@ export default { const session = context.driver.session() await session.run( 'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' + - 'MERGE (post)<-[:WROTE]-(author) ' + - 'RETURN author', { + 'MERGE (post)<-[:WROTE]-(author) ' + + 'RETURN author', + { userId: context.user.id, - postId: result.id - } + postId: result.id, + }, ) session.close() return result - } - } + }, + }, } diff --git a/backend/src/resolvers/posts.spec.js b/backend/src/resolvers/posts.spec.js index 5603683eb..e4175ff09 100644 --- a/backend/src/resolvers/posts.spec.js +++ b/backend/src/resolvers/posts.spec.js @@ -8,7 +8,7 @@ let client beforeEach(async () => { await factory.create('User', { email: 'test@example.org', - password: '1234' + password: '1234', }) }) @@ -47,22 +47,25 @@ describe('CreatePost', () => { const expected = { CreatePost: { title: 'I am a title', - content: 'Some content' - } + content: 'Some content', + }, } await expect(client.request(mutation)).resolves.toMatchObject(expected) }) it('assigns the authenticated user as author', async () => { await client.request(mutation) - const { User } = await client.request(`{ + const { User } = await client.request( + `{ User(email:"test@example.org") { contributions { title } } - }`, { headers }) - expect(User).toEqual([ { contributions: [ { title: 'I am a title' } ] } ]) + }`, + { headers }, + ) + expect(User).toEqual([{ contributions: [{ title: 'I am a title' }] }]) }) describe('disabled and deleted', () => { @@ -86,22 +89,22 @@ describe('UpdatePost', () => { let variables = { id: 'p1', - content: 'New content' + content: 'New content', } beforeEach(async () => { const asAuthor = Factory() await asAuthor.create('User', { email: 'author@example.org', - password: '1234' + password: '1234', }) await asAuthor.authenticateAs({ email: 'author@example.org', - password: '1234' + password: '1234', }) await asAuthor.create('Post', { id: 'p1', - content: 'Old content' + content: 'Old content', }) }) @@ -149,22 +152,22 @@ describe('DeletePost', () => { ` let variables = { - id: 'p1' + id: 'p1', } beforeEach(async () => { const asAuthor = Factory() await asAuthor.create('User', { email: 'author@example.org', - password: '1234' + password: '1234', }) await asAuthor.authenticateAs({ email: 'author@example.org', - password: '1234' + password: '1234', }) await asAuthor.create('Post', { id: 'p1', - content: 'To be deleted' + content: 'To be deleted', }) }) diff --git a/backend/src/resolvers/reports.js b/backend/src/resolvers/reports.js index fb912a557..2c0fbfc75 100644 --- a/backend/src/resolvers/reports.js +++ b/backend/src/resolvers/reports.js @@ -7,11 +7,12 @@ export default { const session = driver.session() const reportData = { id: reportId, - createdAt: (new Date()).toISOString(), - description: description + createdAt: new Date().toISOString(), + description: description, } - const res = await session.run(` + const res = await session.run( + ` MATCH (submitter:User {id: $userId}) MATCH (resource {id: $resourceId}) WHERE resource:User OR resource:Comment OR resource:Post @@ -19,11 +20,12 @@ export default { MERGE (resource)<-[:REPORTED]-(report) MERGE (report)<-[:REPORTED]-(submitter) RETURN report, submitter, resource, labels(resource)[0] as type - `, { - resourceId: id, - userId: user.id, - reportData - } + `, + { + resourceId: id, + userId: user.id, + reportData, + }, ) session.close() @@ -32,7 +34,7 @@ export default { report: r.get('report'), submitter: r.get('submitter'), resource: r.get('resource'), - type: r.get('type') + type: r.get('type'), } }) if (!dbResponse) return null @@ -44,20 +46,20 @@ export default { comment: null, user: null, submitter: submitter.properties, - type + type, } switch (type) { - case 'Post': - response.post = resource.properties - break - case 'Comment': - response.comment = resource.properties - break - case 'User': - response.user = resource.properties - break + case 'Post': + response.post = resource.properties + break + case 'Comment': + response.comment = resource.properties + break + case 'User': + response.user = resource.properties + break } return response - } - } + }, + }, } diff --git a/backend/src/resolvers/reports.spec.js b/backend/src/resolvers/reports.spec.js index 9bd1fe753..19b5e6f9e 100644 --- a/backend/src/resolvers/reports.spec.js +++ b/backend/src/resolvers/reports.spec.js @@ -18,13 +18,13 @@ describe('report', () => { await factory.create('User', { id: 'u1', email: 'test@example.org', - password: '1234' + password: '1234', }) await factory.create('User', { id: 'u2', name: 'abusive-user', role: 'user', - email: 'abusive-user@example.org' + email: 'abusive-user@example.org', }) }) @@ -59,7 +59,7 @@ describe('report', () => { describe('invalid resource id', () => { it('returns null', async () => { await expect(action()).resolves.toEqual({ - report: null + report: null, }) }) }) @@ -71,14 +71,14 @@ describe('report', () => { it('creates a report', async () => { await expect(action()).resolves.toEqual({ - report: { description: 'Violates code of conduct' } + report: { description: 'Violates code of conduct' }, }) }) it('returns the submitter', async () => { returnedObject = '{ submitter { email } }' await expect(action()).resolves.toEqual({ - report: { submitter: { email: 'test@example.org' } } + report: { submitter: { email: 'test@example.org' } }, }) }) @@ -86,14 +86,14 @@ describe('report', () => { it('returns type "User"', async () => { returnedObject = '{ type }' await expect(action()).resolves.toEqual({ - report: { type: 'User' } + report: { type: 'User' }, }) }) it('returns resource in user attribute', async () => { returnedObject = '{ user { name } }' await expect(action()).resolves.toEqual({ - report: { user: { name: 'abusive-user' } } + report: { user: { name: 'abusive-user' } }, }) }) }) @@ -101,28 +101,31 @@ describe('report', () => { describe('reported resource is a post', () => { beforeEach(async () => { await factory.authenticateAs({ email: 'test@example.org', password: '1234' }) - await factory.create('Post', { id: 'p23', title: 'Matt and Robert having a pair-programming' }) + await factory.create('Post', { + id: 'p23', + title: 'Matt and Robert having a pair-programming', + }) variables = { id: 'p23' } }) it('returns type "Post"', async () => { returnedObject = '{ type }' await expect(action()).resolves.toEqual({ - report: { type: 'Post' } + report: { type: 'Post' }, }) }) it('returns resource in post attribute', async () => { returnedObject = '{ post { title } }' await expect(action()).resolves.toEqual({ - report: { post: { title: 'Matt and Robert having a pair-programming' } } + report: { post: { title: 'Matt and Robert having a pair-programming' } }, }) }) it('returns null in user attribute', async () => { returnedObject = '{ user { name } }' await expect(action()).resolves.toEqual({ - report: { user: null } + report: { user: null }, }) }) }) @@ -132,25 +135,32 @@ describe('report', () => { createPostVariables = { id: 'p1', title: 'post to comment on', - content: 'please comment on me' + content: 'please comment on me', } - const asAuthenticatedUser = await factory.authenticateAs({ email: 'test@example.org', password: '1234' }) + const asAuthenticatedUser = await factory.authenticateAs({ + email: 'test@example.org', + password: '1234', + }) await asAuthenticatedUser.create('Post', createPostVariables) - await asAuthenticatedUser.create('Comment', { postId: 'p1', id: 'c34', content: 'Robert getting tired.' }) + await asAuthenticatedUser.create('Comment', { + postId: 'p1', + id: 'c34', + content: 'Robert getting tired.', + }) variables = { id: 'c34' } }) it('returns type "Comment"', async () => { returnedObject = '{ type }' await expect(action()).resolves.toEqual({ - report: { type: 'Comment' } + report: { type: 'Comment' }, }) }) it('returns resource in comment attribute', async () => { returnedObject = '{ comment { content } }' await expect(action()).resolves.toEqual({ - report: { comment: { content: 'Robert getting tired.' } } + report: { comment: { content: 'Robert getting tired.' } }, }) }) }) diff --git a/backend/src/resolvers/rewards.js b/backend/src/resolvers/rewards.js index a7a8c1ab7..ec5043da3 100644 --- a/backend/src/resolvers/rewards.js +++ b/backend/src/resolvers/rewards.js @@ -10,8 +10,8 @@ export default { RETURN rewardedUser {.id}`, { badgeId: fromBadgeId, - rewardedUserId: toUserId - } + rewardedUserId: toUserId, + }, ) const [rewardedUser] = transactionRes.records.map(record => { @@ -33,8 +33,8 @@ export default { RETURN rewardedUser {.id}`, { badgeId: fromBadgeId, - rewardedUserId: toUserId - } + rewardedUserId: toUserId, + }, ) const [rewardedUser] = transactionRes.records.map(record => { return record.get('rewardedUser') @@ -42,6 +42,6 @@ export default { session.close() return rewardedUser.id - } - } + }, + }, } diff --git a/backend/src/resolvers/rewards.spec.js b/backend/src/resolvers/rewards.spec.js index 567228eca..e2b67b25d 100644 --- a/backend/src/resolvers/rewards.spec.js +++ b/backend/src/resolvers/rewards.spec.js @@ -10,24 +10,24 @@ describe('rewards', () => { id: 'u1', role: 'user', email: 'user@example.org', - password: '1234' + password: '1234', }) await factory.create('User', { id: 'u2', role: 'moderator', - email: 'moderator@example.org' + email: 'moderator@example.org', }) await factory.create('User', { id: 'u3', role: 'admin', - email: 'admin@example.org' + email: 'admin@example.org', }) await factory.create('Badge', { id: 'b6', key: 'indiegogo_en_rhino', type: 'crowdfunding', status: 'permanent', - icon: '/img/badges/indiegogo_en_rhino.svg' + icon: '/img/badges/indiegogo_en_rhino.svg', }) }) @@ -48,15 +48,13 @@ describe('rewards', () => { describe('unauthenticated', () => { const variables = { from: 'b6', - to: 'u1' + to: 'u1', } let client it('throws authorization error', async () => { client = new GraphQLClient(host) - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -70,14 +68,12 @@ describe('rewards', () => { it('rewards a badge to user', async () => { const variables = { from: 'b6', - to: 'u1' + to: 'u1', } const expected = { - reward: 'u1' + reward: 'u1', } - 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 () => { await factory.create('Badge', { @@ -85,41 +81,37 @@ describe('rewards', () => { 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' + to: 'u1', } const expected = { - reward: 'u1' + reward: 'u1', } - await expect( - client.request(mutation, variables) - ).resolves.toEqual(expected) + await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) it('rewards the same badge as well to another user', async () => { const variables1 = { from: 'b6', - to: 'u1' + to: 'u1', } await client.request(mutation, variables1) const variables2 = { from: 'b6', - to: 'u2' + to: 'u2', } const expected = { - reward: 'u2' + reward: 'u2', } - await expect( - client.request(mutation, variables2) - ).resolves.toEqual(expected) + await expect(client.request(mutation, variables2)).resolves.toEqual(expected) }) it('returns the original reward if a reward is attempted a second time', async () => { const variables = { from: 'b6', - to: 'u1' + to: 'u1', } await client.request(mutation, variables) await client.request(mutation, variables) @@ -132,16 +124,14 @@ describe('rewards', () => { ` const expected = { User: [{ badgesCount: 1 }] } - await expect( - client.request(query) - ).resolves.toEqual(expected) + await expect(client.request(query)).resolves.toEqual(expected) }) }) describe('authenticated moderator', () => { const variables = { from: 'b6', - to: 'u1' + to: 'u1', } let client beforeEach(async () => { @@ -151,9 +141,7 @@ describe('rewards', () => { describe('rewards bage to user', () => { it('throws authorization error', async () => { - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) }) @@ -165,10 +153,10 @@ describe('rewards', () => { }) const variables = { from: 'b6', - to: 'u1' + to: 'u1', } const expected = { - unreward: 'u1' + unreward: 'u1', } const mutation = ` @@ -185,9 +173,7 @@ describe('rewards', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -199,17 +185,15 @@ describe('rewards', () => { }) it('removes a badge from user', async () => { - 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 () => { 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)).rejects.toThrow( + "Cannot read property 'id' of undefined", + ) }) }) @@ -222,9 +206,7 @@ describe('rewards', () => { describe('removes bage from user', () => { it('throws authorization error', async () => { - await expect( - client.request(mutation, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') }) }) }) diff --git a/backend/src/resolvers/shout.js b/backend/src/resolvers/shout.js index 69c39a3a9..d2d7f652e 100644 --- a/backend/src/resolvers/shout.js +++ b/backend/src/resolvers/shout.js @@ -12,8 +12,8 @@ export default { { id, type, - userId: context.user.id - } + userId: context.user.id, + }, ) const [isShouted] = transactionRes.records.map(record => { @@ -37,8 +37,8 @@ export default { { id, type, - userId: context.user.id - } + userId: context.user.id, + }, ) const [isShouted] = transactionRes.records.map(record => { return record.get('isShouted') @@ -46,6 +46,6 @@ export default { session.close() return isShouted - } - } + }, + }, } diff --git a/backend/src/resolvers/shout.spec.js b/backend/src/resolvers/shout.spec.js index 88866a74f..46570efa0 100644 --- a/backend/src/resolvers/shout.spec.js +++ b/backend/src/resolvers/shout.spec.js @@ -6,12 +6,12 @@ const factory = Factory() let clientUser1, clientUser2 let headersUser1, headersUser2 -const mutationShoutPost = (id) => ` +const mutationShoutPost = id => ` mutation { shout(id: "${id}", type: Post) } ` -const mutationUnshoutPost = (id) => ` +const mutationUnshoutPost = id => ` mutation { unshout(id: "${id}", type: Post) } @@ -21,12 +21,12 @@ beforeEach(async () => { await factory.create('User', { id: 'u1', email: 'test@example.org', - password: '1234' + password: '1234', }) await factory.create('User', { id: 'u2', email: 'test2@example.org', - password: '1234' + password: '1234', }) headersUser1 = await login({ email: 'test@example.org', password: '1234' }) @@ -62,18 +62,14 @@ describe('shout', () => { it('throws authorization error', async () => { let client client = new GraphQLClient(host) - await expect( - client.request(mutationShoutPost('p1')) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutationShoutPost('p1'))).rejects.toThrow('Not Authorised') }) }) it('I shout a post of another user', async () => { - const res = await clientUser1.request( - mutationShoutPost('p2') - ) + const res = await clientUser1.request(mutationShoutPost('p2')) const expected = { - shout: true + shout: true, } expect(res).toMatchObject(expected) @@ -83,17 +79,15 @@ describe('shout', () => { } }`) const expected2 = { - shoutedByCurrentUser: true + shoutedByCurrentUser: true, } expect(Post[0]).toMatchObject(expected2) }) it('I can`t shout my own post', async () => { - const res = await clientUser1.request( - mutationShoutPost('p1') - ) + const res = await clientUser1.request(mutationShoutPost('p1')) const expected = { - shout: false + shout: false, } expect(res).toMatchObject(expected) @@ -103,7 +97,7 @@ describe('shout', () => { } }`) const expected2 = { - shoutedByCurrentUser: false + shoutedByCurrentUser: false, } expect(Post[0]).toMatchObject(expected2) }) @@ -113,25 +107,19 @@ describe('shout', () => { describe('unauthenticated shout', () => { it('throws authorization error', async () => { // shout - await clientUser1.request( - mutationShoutPost('p2') - ) + await clientUser1.request(mutationShoutPost('p2')) // unshout let client client = new GraphQLClient(host) - await expect( - client.request(mutationUnshoutPost('p2')) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutationUnshoutPost('p2'))).rejects.toThrow('Not Authorised') }) }) it('I unshout a post of another user', async () => { // shout - await clientUser1.request( - mutationShoutPost('p2') - ) + await clientUser1.request(mutationShoutPost('p2')) const expected = { - unshout: true + unshout: true, } // unshout const res = await clientUser1.request(mutationUnshoutPost('p2')) @@ -143,7 +131,7 @@ describe('shout', () => { } }`) const expected2 = { - shoutedByCurrentUser: false + shoutedByCurrentUser: false, } expect(Post[0]).toMatchObject(expected2) }) diff --git a/backend/src/resolvers/socialMedia.js b/backend/src/resolvers/socialMedia.js index ef143a478..0bc03ea74 100644 --- a/backend/src/resolvers/socialMedia.js +++ b/backend/src/resolvers/socialMedia.js @@ -5,16 +5,17 @@ export default { CreateSocialMedia: async (object, params, context, resolveInfo) => { /** * TODO?: Creates double Nodes! - */ + */ 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`, { + RETURN owner`, + { userId: context.user.id, - socialMediaId: socialMedia.id - } + socialMediaId: socialMedia.id, + }, ) session.close() @@ -24,6 +25,6 @@ export default { const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false) return socialMedia - } - } + }, + }, } diff --git a/backend/src/resolvers/socialMedia.spec.js b/backend/src/resolvers/socialMedia.spec.js index 9d1d76726..5143587b1 100644 --- a/backend/src/resolvers/socialMedia.spec.js +++ b/backend/src/resolvers/socialMedia.spec.js @@ -31,7 +31,7 @@ describe('CreateSocialMedia', () => { slug: 'matilde-hermiston', role: 'user', email: 'test@example.org', - password: '1234' + password: '1234', }) }) @@ -43,9 +43,7 @@ describe('CreateSocialMedia', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) const variables = { url: 'http://nsosp.org' } - await expect( - client.request(mutationC, variables) - ).rejects.toThrow('Not Authorised') + await expect(client.request(mutationC, variables)).rejects.toThrow('Not Authorised') }) }) @@ -57,14 +55,14 @@ describe('CreateSocialMedia', () => { it('creates social media with correct URL', async () => { const variables = { url: 'http://nsosp.org' } - await expect( - client.request(mutationC, variables) - ).resolves.toEqual(expect.objectContaining({ - CreateSocialMedia: { - id: expect.any(String), - url: 'http://nsosp.org' - } - })) + await expect(client.request(mutationC, variables)).resolves.toEqual( + expect.objectContaining({ + CreateSocialMedia: { + id: expect.any(String), + url: 'http://nsosp.org', + }, + }), + ) }) it('deletes social media', async () => { @@ -76,26 +74,20 @@ describe('CreateSocialMedia', () => { const expected = { DeleteSocialMedia: { id: id, - url: 'http://nsosp.org' - } + url: 'http://nsosp.org', + }, } - await expect( - client.request(mutationD, deletionVariables) - ).resolves.toEqual(expected) + await expect(client.request(mutationD, deletionVariables)).resolves.toEqual(expected) }) it('rejects empty string', async () => { const variables = { url: '' } - await expect( - client.request(mutationC, variables) - ).rejects.toThrow('Input is not a URL') + await expect(client.request(mutationC, variables)).rejects.toThrow('Input is not a URL') }) it('validates URLs', async () => { const variables = { url: 'not-a-url' } - await expect( - client.request(mutationC, variables) - ).rejects.toThrow('Input is not a URL') + await expect(client.request(mutationC, variables)).rejects.toThrow('Input is not a URL') }) }) }) diff --git a/backend/src/resolvers/statistics.js b/backend/src/resolvers/statistics.js index 17c4be956..f09b7219d 100644 --- a/backend/src/resolvers/statistics.js +++ b/backend/src/resolvers/statistics.js @@ -1,24 +1,22 @@ export const query = (cypher, session) => { return new Promise((resolve, reject) => { let data = [] - session - .run(cypher) - .subscribe({ - onNext: function (record) { - let item = {} - record.keys.forEach(key => { - item[key] = record.get(key) - }) - data.push(item) - }, - onCompleted: function () { - session.close() - resolve(data) - }, - onError: function (error) { - reject(error) - } - }) + session.run(cypher).subscribe({ + onNext: function(record) { + let item = {} + record.keys.forEach(key => { + item[key] = record.get(key) + }) + data.push(item) + }, + onCompleted: function() { + session.close() + resolve(data) + }, + onError: function(error) { + reject(error) + }, + }) }) } const queryOne = (cypher, session) => { @@ -36,32 +34,41 @@ const queryOne = (cypher, session) => { export default { Query: { statistics: async (parent, args, { driver, user }) => { - return new Promise(async (resolve) => { + return new Promise(async resolve => { const session = driver.session() const queries = { - countUsers: 'MATCH (r:User) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countUsers', - countPosts: 'MATCH (r:Post) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countPosts', - countComments: 'MATCH (r:Comment) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countComments', - countNotifications: 'MATCH (r:Notification) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countNotifications', - countOrganizations: 'MATCH (r:Organization) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countOrganizations', - countProjects: 'MATCH (r:Project) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countProjects', - countInvites: 'MATCH (r:Invite) WHERE r.wasUsed <> true OR NOT exists(r.wasUsed) RETURN COUNT(r) AS countInvites', + countUsers: + 'MATCH (r:User) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countUsers', + countPosts: + 'MATCH (r:Post) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countPosts', + countComments: + 'MATCH (r:Comment) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countComments', + countNotifications: + 'MATCH (r:Notification) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countNotifications', + countOrganizations: + 'MATCH (r:Organization) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countOrganizations', + countProjects: + 'MATCH (r:Project) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countProjects', + countInvites: + 'MATCH (r:Invite) WHERE r.wasUsed <> true OR NOT exists(r.wasUsed) RETURN COUNT(r) AS countInvites', countFollows: 'MATCH (:User)-[r:FOLLOWS]->(:User) RETURN COUNT(r) AS countFollows', - countShouts: 'MATCH (:User)-[r:SHOUTED]->(:Post) RETURN COUNT(r) AS countShouts' + countShouts: 'MATCH (:User)-[r:SHOUTED]->(:Post) RETURN COUNT(r) AS countShouts', } let data = { countUsers: (await queryOne(queries.countUsers, session)).countUsers.low, countPosts: (await queryOne(queries.countPosts, session)).countPosts.low, countComments: (await queryOne(queries.countComments, session)).countComments.low, - countNotifications: (await queryOne(queries.countNotifications, session)).countNotifications.low, - countOrganizations: (await queryOne(queries.countOrganizations, session)).countOrganizations.low, + countNotifications: (await queryOne(queries.countNotifications, session)) + .countNotifications.low, + countOrganizations: (await queryOne(queries.countOrganizations, session)) + .countOrganizations.low, countProjects: (await queryOne(queries.countProjects, session)).countProjects.low, countInvites: (await queryOne(queries.countInvites, session)).countInvites.low, countFollows: (await queryOne(queries.countFollows, session)).countFollows.low, - countShouts: (await queryOne(queries.countShouts, session)).countShouts.low + countShouts: (await queryOne(queries.countShouts, session)).countShouts.low, } resolve(data) }) - } - } + }, + }, } diff --git a/backend/src/resolvers/user_management.js b/backend/src/resolvers/user_management.js index 26dfb81db..e2fd5acf1 100644 --- a/backend/src/resolvers/user_management.js +++ b/backend/src/resolvers/user_management.js @@ -12,7 +12,7 @@ export default { const { user } = ctx if (!user) return null return neo4jgraphql(object, { id: user.id }, ctx, resolveInfo, false) - } + }, }, Mutation: { signup: async (parent, { email, password }, { req }) => { @@ -34,12 +34,12 @@ export default { 'MATCH (user:User {email: $userEmail}) ' + 'RETURN user {.id, .slug, .name, .avatar, .email, .password, .role, .disabled} as user LIMIT 1', { - userEmail: email - } + userEmail: email, + }, ) session.close() - const [currentUser] = await result.records.map(function (record) { + const [currentUser] = await result.records.map(function(record) { return record.get('user') }) @@ -50,29 +50,23 @@ export default { ) { delete currentUser.password return encode(currentUser) - } else if (currentUser && - currentUser.disabled - ) { + } else if (currentUser && currentUser.disabled) { throw new AuthenticationError('Your account has been disabled.') } else { throw new AuthenticationError('Incorrect email address or password.') } }, - changePassword: async ( - _, - { oldPassword, newPassword }, - { driver, user } - ) => { + changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => { const session = driver.session() let result = await session.run( `MATCH (user:User {email: $userEmail}) RETURN user {.id, .email, .password}`, { - userEmail: user.email - } + userEmail: user.email, + }, ) - const [currentUser] = result.records.map(function (record) { + const [currentUser] = result.records.map(function(record) { return record.get('user') }) @@ -81,9 +75,7 @@ export default { } if (await bcrypt.compareSync(newPassword, currentUser.password)) { - throw new AuthenticationError( - 'Old password and new password should be different' - ) + throw new AuthenticationError('Old password and new password should be different') } else { const newHashedPassword = await bcrypt.hashSync(newPassword, 10) session.run( @@ -93,13 +85,13 @@ export default { `, { userEmail: user.email, - newHashedPassword - } + newHashedPassword, + }, ) session.close() return encode(currentUser) } - } - } + }, + }, } diff --git a/backend/src/resolvers/user_management.spec.js b/backend/src/resolvers/user_management.spec.js index 7c0be08f3..9dff9e388 100644 --- a/backend/src/resolvers/user_management.spec.js +++ b/backend/src/resolvers/user_management.spec.js @@ -24,10 +24,10 @@ const factory = Factory() // } const jennyRostocksHeaders = { authorization: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc' + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc', } -const disable = async (id) => { +const disable = async id => { const moderatorParams = { email: 'moderator@example.org', role: 'moderator', password: '1234' } const asModerator = Factory() await asModerator.create('User', moderatorParams) @@ -43,7 +43,7 @@ beforeEach(async () => { slug: 'matilde-hermiston', role: 'user', email: 'test@example.org', - password: '1234' + password: '1234', }) }) @@ -56,7 +56,7 @@ describe('isLoggedIn', () => { describe('unauthenticated', () => { it('returns false', async () => { await expect(request(host, query)).resolves.toEqual({ - isLoggedIn: false + isLoggedIn: false, }) }) }) @@ -67,7 +67,7 @@ describe('isLoggedIn', () => { it('returns false', async () => { await expect(client.request(query)).resolves.toEqual({ - isLoggedIn: false + isLoggedIn: false, }) }) }) @@ -77,7 +77,7 @@ describe('isLoggedIn', () => { it('returns false', async () => { await expect(client.request(query)).resolves.toEqual({ - isLoggedIn: false + isLoggedIn: false, }) }) @@ -87,7 +87,7 @@ describe('isLoggedIn', () => { // see the decoded token above await factory.create('User', { id: 'u3' }) await expect(client.request(query)).resolves.toEqual({ - isLoggedIn: true + isLoggedIn: true, }) }) }) @@ -100,7 +100,7 @@ describe('isLoggedIn', () => { it('returns false', async () => { await expect(client.request(query)).resolves.toEqual({ - isLoggedIn: false + isLoggedIn: false, }) }) }) @@ -156,8 +156,8 @@ describe('currentUser', () => { id: 'acb2d923-f3af-479e-9f00-61b12e864666', name: 'Matilde Hermiston', slug: 'matilde-hermiston', - role: 'user' - } + role: 'user', + }, } await expect(client.request(query)).resolves.toEqual(expected) }) @@ -181,8 +181,8 @@ describe('login', () => { host, mutation({ email: 'test@example.org', - password: '1234' - }) + password: '1234', + }), ) const token = data.login jwt.verify(token, process.env.JWT_SECRET, (err, data) => { @@ -200,9 +200,9 @@ describe('login', () => { host, mutation({ email: 'test@example.org', - password: '1234' - }) - ) + password: '1234', + }), + ), ).rejects.toThrow('Your account has been disabled.') }) }) @@ -214,9 +214,9 @@ describe('login', () => { host, mutation({ email: 'test@example.org', - password: 'wrong' - }) - ) + password: 'wrong', + }), + ), ).rejects.toThrow('Incorrect email address or password.') }) }) @@ -228,9 +228,9 @@ describe('login', () => { host, mutation({ email: 'non-existent@example.org', - password: 'wrong' - }) - ) + password: 'wrong', + }), + ), ).rejects.toThrow('Incorrect email address or password.') }) }) @@ -261,9 +261,9 @@ describe('change password', () => { host, mutation({ oldPassword: '1234', - newPassword: '1234' - }) - ) + newPassword: '1234', + }), + ), ).rejects.toThrow('Not Authorised!') }) }) @@ -274,9 +274,9 @@ describe('change password', () => { client.request( mutation({ oldPassword: '1234', - newPassword: '1234' - }) - ) + newPassword: '1234', + }), + ), ).rejects.toThrow('Old password and new password should be different') }) }) @@ -287,9 +287,9 @@ describe('change password', () => { client.request( mutation({ oldPassword: 'notOldPassword', - newPassword: '12345' - }) - ) + newPassword: '12345', + }), + ), ).rejects.toThrow('Old password is not correct') }) }) @@ -299,14 +299,14 @@ describe('change password', () => { let response = await client.request( mutation({ oldPassword: '1234', - newPassword: '12345' - }) + newPassword: '12345', + }), + ) + await expect(response).toEqual( + expect.objectContaining({ + changePassword: expect.any(String), + }), ) - await expect( - response - ).toEqual(expect.objectContaining({ - changePassword: expect.any(String) - })) }) }) }) @@ -320,14 +320,16 @@ describe('do not expose private RSA key', () => { id publicKey } - }` + } + ` const queryUserPrivateKey = gql` query($queriedUserSlug: String) { User(slug: $queriedUserSlug) { id privateKey } - }` + } + ` const actionGenUserWithKeys = async () => { // Generate user with "privateKey" via 'CreateUser' mutation instead of using the factories "factory.create('User', {...})", see above. @@ -336,14 +338,17 @@ describe('do not expose private RSA key', () => { password: 'xYz', slug: 'apfel-strudel', name: 'Apfel Strudel', - email: 'apfel-strudel@test.org' + email: 'apfel-strudel@test.org', } - await client.request(gql` - mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String!) { - CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) { - id + await client.request( + gql` + mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String!) { + CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) { + id + } } - }`, variables + `, + variables, ) } @@ -356,13 +361,17 @@ describe('do not expose private RSA key', () => { it('returns publicKey', async () => { await actionGenUserWithKeys() await expect( - await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }) - ).toEqual(expect.objectContaining({ - User: [{ - id: 'bcb2d923-f3af-479e-9f00-61b12e864667', - publicKey: expect.any(String) - }] - })) + await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }), + ).toEqual( + expect.objectContaining({ + User: [ + { + id: 'bcb2d923-f3af-479e-9f00-61b12e864667', + publicKey: expect.any(String), + }, + ], + }), + ) }) }) @@ -370,7 +379,7 @@ describe('do not expose private RSA key', () => { it('throws "Not Authorised!"', async () => { await actionGenUserWithKeys() await expect( - client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }) + client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }), ).rejects.toThrow('Not Authorised') }) }) @@ -385,13 +394,17 @@ describe('do not expose private RSA key', () => { it('returns publicKey', async () => { await actionGenUserWithKeys() await expect( - await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }) - ).toEqual(expect.objectContaining({ - User: [{ - id: 'bcb2d923-f3af-479e-9f00-61b12e864667', - publicKey: expect.any(String) - }] - })) + await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }), + ).toEqual( + expect.objectContaining({ + User: [ + { + id: 'bcb2d923-f3af-479e-9f00-61b12e864667', + publicKey: expect.any(String), + }, + ], + }), + ) }) }) @@ -399,7 +412,7 @@ describe('do not expose private RSA key', () => { it('throws "Not Authorised!"', async () => { await actionGenUserWithKeys() await expect( - client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }) + client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }), ).rejects.toThrow('Not Authorised') }) }) diff --git a/backend/src/resolvers/users.spec.js b/backend/src/resolvers/users.spec.js index 48e4741d7..bf55784fd 100644 --- a/backend/src/resolvers/users.spec.js +++ b/backend/src/resolvers/users.spec.js @@ -24,15 +24,14 @@ describe('users', () => { const variables = { name: 'John Doe', password: '123', - email: '123@123.de' + email: '123@123.de', } const expected = { CreateUser: { - id: expect.any(String) - } + id: expect.any(String), + }, } - await expect(client.request(mutation, variables)) - .resolves.toEqual(expected) + await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) }) @@ -54,35 +53,32 @@ describe('users', () => { it('name within specifications', async () => { const variables = { id: 'u47', - name: 'James Doe' + name: 'James Doe', } const expected = { UpdateUser: { id: 'u47', - name: 'James Doe' - } + name: 'James Doe', + }, } - await expect(client.request(mutation, variables)) - .resolves.toEqual(expected) + await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) it('with no name', async () => { const variables = { - id: 'u47' + id: 'u47', } const expected = 'Username must be at least 3 characters long!' - await expect(client.request(mutation, variables)) - .rejects.toThrow(expected) + await expect(client.request(mutation, variables)).rejects.toThrow(expected) }) it('with too short name', async () => { const variables = { id: 'u47', - name: ' ' + name: ' ', } const expected = 'Username must be at least 3 characters long!' - await expect(client.request(mutation, variables)) - .rejects.toThrow(expected) + await expect(client.request(mutation, variables)).rejects.toThrow(expected) }) }) }) diff --git a/backend/src/seed/factories/badges.js b/backend/src/seed/factories/badges.js index 6f5f8d69a..52fa9da33 100644 --- a/backend/src/seed/factories/badges.js +++ b/backend/src/seed/factories/badges.js @@ -1,12 +1,12 @@ import uuid from 'uuid/v4' -export default function (params) { +export default function(params) { const { id = uuid(), key = '', type = 'crowdfunding', status = 'permanent', - icon = '/img/badges/indiegogo_en_panda.svg' + icon = '/img/badges/indiegogo_en_panda.svg', } = params return { @@ -23,6 +23,6 @@ export default function (params) { } } `, - variables: { id, key, type, status, icon } + variables: { id, key, type, status, icon }, } } diff --git a/backend/src/seed/factories/categories.js b/backend/src/seed/factories/categories.js index 5c1b3ce10..341f1b1fd 100644 --- a/backend/src/seed/factories/categories.js +++ b/backend/src/seed/factories/categories.js @@ -1,12 +1,7 @@ import uuid from 'uuid/v4' -export default function (params) { - const { - id = uuid(), - name, - slug, - icon - } = params +export default function(params) { + const { id = uuid(), name, slug, icon } = params return { mutation: ` @@ -17,6 +12,6 @@ export default function (params) { } } `, - variables: { id, name, slug, icon } + variables: { id, name, slug, icon }, } } diff --git a/backend/src/seed/factories/comments.js b/backend/src/seed/factories/comments.js index ba3a85840..b1079e392 100644 --- a/backend/src/seed/factories/comments.js +++ b/backend/src/seed/factories/comments.js @@ -1,14 +1,11 @@ import faker from 'faker' import uuid from 'uuid/v4' -export default function (params) { +export default function(params) { const { id = uuid(), postId = 'p6', - content = [ - faker.lorem.sentence(), - faker.lorem.sentence() - ].join('. ') + content = [faker.lorem.sentence(), faker.lorem.sentence()].join('. '), } = params return { @@ -19,6 +16,6 @@ export default function (params) { } } `, - variables: { id, postId, content } + variables: { id, postId, content }, } } diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js index a0cb310ab..211edf87e 100644 --- a/backend/src/seed/factories/index.js +++ b/backend/src/seed/factories/index.js @@ -19,7 +19,7 @@ const authenticatedHeaders = async ({ email, password }, host) => { }` const response = await request(host, mutation) return { - authorization: `Bearer ${response.login}` + authorization: `Bearer ${response.login}`, } } const factories = { @@ -31,7 +31,7 @@ const factories = { Category: createCategory, Tag: createTag, Report: createReport, - Notification: createNotification + Notification: createNotification, } export const cleanDatabase = async (options = {}) => { @@ -47,11 +47,8 @@ export const cleanDatabase = async (options = {}) => { } } -export default function Factory (options = {}) { - const { - neo4jDriver = getDriver(), - seedServerHost = 'http://127.0.0.1:4001' - } = options +export default function Factory(options = {}) { + const { neo4jDriver = getDriver(), seedServerHost = 'http://127.0.0.1:4001' } = options const graphQLClient = new GraphQLClient(seedServerHost) @@ -61,21 +58,18 @@ export default function Factory (options = {}) { graphQLClient, factories, lastResponse: null, - async authenticateAs ({ email, password }) { - const headers = await authenticatedHeaders( - { email, password }, - seedServerHost - ) + async authenticateAs({ email, password }) { + const headers = await authenticatedHeaders({ email, password }, seedServerHost) this.lastResponse = headers this.graphQLClient = new GraphQLClient(seedServerHost, { headers }) return this }, - async create (node, properties) { + async create(node, properties) { const { mutation, variables } = this.factories[node](properties) this.lastResponse = await this.graphQLClient.request(mutation, variables) return this }, - async relate (node, relationship, properties) { + async relate(node, relationship, properties) { const { from, to } = properties const mutation = ` mutation { @@ -88,11 +82,11 @@ export default function Factory (options = {}) { this.lastResponse = await this.graphQLClient.request(mutation) return this }, - async mutate (mutation, variables) { + async mutate(mutation, variables) { this.lastResponse = await this.graphQLClient.request(mutation, variables) return this }, - async shout (properties) { + async shout(properties) { const { id, type } = properties const mutation = ` mutation { @@ -105,7 +99,7 @@ export default function Factory (options = {}) { this.lastResponse = await this.graphQLClient.request(mutation) return this }, - async follow (properties) { + async follow(properties) { const { id, type } = properties const mutation = ` mutation { @@ -118,10 +112,10 @@ export default function Factory (options = {}) { this.lastResponse = await this.graphQLClient.request(mutation) return this }, - async cleanDatabase () { + async cleanDatabase() { this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver }) return this - } + }, } result.authenticateAs.bind(result) result.create.bind(result) diff --git a/backend/src/seed/factories/notifications.js b/backend/src/seed/factories/notifications.js index f7797200f..d14d4294a 100644 --- a/backend/src/seed/factories/notifications.js +++ b/backend/src/seed/factories/notifications.js @@ -1,10 +1,7 @@ import uuid from 'uuid/v4' -export default function (params) { - const { - id = uuid(), - read = false - } = params +export default function(params) { + const { id = uuid(), read = false } = params return { mutation: ` @@ -15,6 +12,6 @@ export default function (params) { } } `, - variables: { id, read } + variables: { id, read }, } } diff --git a/backend/src/seed/factories/organizations.js b/backend/src/seed/factories/organizations.js index dd4100b26..536de1597 100644 --- a/backend/src/seed/factories/organizations.js +++ b/backend/src/seed/factories/organizations.js @@ -1,11 +1,11 @@ import faker from 'faker' import uuid from 'uuid/v4' -export default function create (params) { +export default function create(params) { const { id = uuid(), name = faker.company.companyName(), - description = faker.company.catchPhrase() + description = faker.company.catchPhrase(), } = params return { @@ -16,6 +16,6 @@ export default function create (params) { } } `, - variables: { id, name, description } + variables: { id, name, description }, } } diff --git a/backend/src/seed/factories/posts.js b/backend/src/seed/factories/posts.js index cbc73dbf8..1468b05d7 100644 --- a/backend/src/seed/factories/posts.js +++ b/backend/src/seed/factories/posts.js @@ -1,7 +1,7 @@ import faker from 'faker' import uuid from 'uuid/v4' -export default function (params) { +export default function(params) { const { id = uuid(), slug = '', @@ -11,11 +11,11 @@ export default function (params) { faker.lorem.sentence(), faker.lorem.sentence(), faker.lorem.sentence(), - faker.lorem.sentence() + faker.lorem.sentence(), ].join('. '), image = faker.image.image(), visibility = 'public', - deleted = false + deleted = false, } = params return { @@ -43,6 +43,6 @@ export default function (params) { } } `, - variables: { id, slug, title, content, image, visibility, deleted } + variables: { id, slug, title, content, image, visibility, deleted }, } } diff --git a/backend/src/seed/factories/reports.js b/backend/src/seed/factories/reports.js index 130c20c37..40d0e6179 100644 --- a/backend/src/seed/factories/reports.js +++ b/backend/src/seed/factories/reports.js @@ -1,10 +1,7 @@ import faker from 'faker' -export default function create (params) { - const { - description = faker.lorem.sentence(), - id - } = params +export default function create(params) { + const { description = faker.lorem.sentence(), id } = params return { mutation: ` @@ -15,6 +12,6 @@ export default function create (params) { } } `, - variables: { id, description } + variables: { id, description }, } } diff --git a/backend/src/seed/factories/tags.js b/backend/src/seed/factories/tags.js index 558b68957..15ded1986 100644 --- a/backend/src/seed/factories/tags.js +++ b/backend/src/seed/factories/tags.js @@ -1,10 +1,7 @@ import uuid from 'uuid/v4' -export default function (params) { - const { - id = uuid(), - name = '#human-connection' - } = params +export default function(params) { + const { id = uuid(), name = '#human-connection' } = params return { mutation: ` @@ -14,6 +11,6 @@ export default function (params) { } } `, - variables: { id, name } + variables: { id, name }, } } diff --git a/backend/src/seed/factories/users.js b/backend/src/seed/factories/users.js index a088b4c54..1ab362ce2 100644 --- a/backend/src/seed/factories/users.js +++ b/backend/src/seed/factories/users.js @@ -1,7 +1,7 @@ import faker from 'faker' import uuid from 'uuid/v4' -export default function create (params) { +export default function create(params) { const { id = uuid(), name = faker.name.findName(), @@ -10,7 +10,7 @@ export default function create (params) { password = '1234', role = 'user', avatar = faker.internet.avatar(), - about = faker.lorem.paragraph() + about = faker.lorem.paragraph(), } = params return { @@ -46,6 +46,6 @@ export default function create (params) { } } `, - variables: { id, name, slug, password, email, avatar, about, role } + variables: { id, name, slug, password, email, avatar, about, role }, } } diff --git a/backend/src/seed/reset-db.js b/backend/src/seed/reset-db.js index 4075489f9..3197a6e18 100644 --- a/backend/src/seed/reset-db.js +++ b/backend/src/seed/reset-db.js @@ -7,13 +7,13 @@ if (process.env.NODE_ENV === 'production') { throw new Error(`YOU CAN'T CLEAN THE DATABASE WITH NODE_ENV=${process.env.NODE_ENV}`) } -(async function () { +;(async function() { try { await cleanDatabase() - console.log('Successfully deleted all nodes and relations!') + console.log('Successfully deleted all nodes and relations!') // eslint-disable-line no-console process.exit(0) } catch (err) { - console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) + console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) // eslint-disable-line no-console process.exit(1) } })() diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index 8694a7948..27af1106a 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -2,126 +2,247 @@ import faker from 'faker' import Factory from './factories' /* eslint-disable no-multi-spaces */ -(async function () { +;(async function() { try { const f = Factory() 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' }) + 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([ - f.create('User', { id: 'u1', name: 'Peter Lustig', role: 'admin', email: 'admin@example.org' }), - f.create('User', { id: 'u2', name: 'Bob der Baumeister', role: 'moderator', email: 'moderator@example.org' }), - f.create('User', { id: 'u3', name: 'Jenny Rostock', role: 'user', email: 'user@example.org' }), - f.create('User', { id: 'u4', name: 'Tick', role: 'user', email: 'tick@example.org' }), - f.create('User', { id: 'u5', name: 'Trick', role: 'user', email: 'trick@example.org' }), - f.create('User', { id: 'u6', name: 'Track', role: 'user', email: 'track@example.org' }), - f.create('User', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' }) + f.create('User', { + id: 'u1', + name: 'Peter Lustig', + role: 'admin', + email: 'admin@example.org', + }), + f.create('User', { + id: 'u2', + name: 'Bob der Baumeister', + role: 'moderator', + email: 'moderator@example.org', + }), + f.create('User', { + id: 'u3', + name: 'Jenny Rostock', + role: 'user', + email: 'user@example.org', + }), + f.create('User', { id: 'u4', name: 'Tick', role: 'user', email: 'tick@example.org' }), + f.create('User', { id: 'u5', name: 'Trick', role: 'user', email: 'trick@example.org' }), + f.create('User', { id: 'u6', name: 'Track', role: 'user', email: 'track@example.org' }), + f.create('User', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' }), ]) - const [ asAdmin, asModerator, asUser, asTick, asTrick, asTrack ] = await Promise.all([ - Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }), + const [asAdmin, asModerator, asUser, asTick, asTrick, asTrack] = await Promise.all([ + Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }), Factory().authenticateAs({ email: 'moderator@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'user@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'track@example.org', password: '1234' }) + Factory().authenticateAs({ email: 'user@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'track@example.org', password: '1234' }), ]) 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' }), - f.relate('User', 'Friends', { from: 'u1', to: 'u2' }), - f.relate('User', 'Friends', { from: 'u1', to: 'u3' }), - f.relate('User', 'Friends', { from: 'u2', to: 'u3' }), + 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' }), + f.relate('User', 'Friends', { from: 'u1', to: 'u2' }), + f.relate('User', 'Friends', { from: 'u1', to: 'u3' }), + f.relate('User', 'Friends', { from: 'u2', to: 'u3' }), f.relate('User', 'Blacklisted', { from: 'u7', to: 'u4' }), f.relate('User', 'Blacklisted', { from: 'u7', to: 'u5' }), - f.relate('User', 'Blacklisted', { from: 'u7', to: 'u6' }) + f.relate('User', 'Blacklisted', { from: 'u7', to: 'u6' }), ]) await Promise.all([ - asAdmin - .follow({ id: 'u3', type: 'User' }), - asModerator - .follow({ id: 'u4', type: 'User' }), - asUser - .follow({ id: 'u4', type: 'User' }), - asTick - .follow({ id: 'u6', type: 'User' }), - asTrick - .follow({ id: 'u4', type: 'User' }), - asTrack - .follow({ id: 'u3', type: 'User' }) + asAdmin.follow({ id: 'u3', type: 'User' }), + asModerator.follow({ id: 'u4', type: 'User' }), + asUser.follow({ id: 'u4', type: 'User' }), + asTick.follow({ id: 'u6', type: 'User' }), + asTrick.follow({ id: 'u4', type: 'User' }), + asTrack.follow({ id: 'u3', type: 'User' }), ]) await Promise.all([ - f.create('Category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }), - f.create('Category', { id: 'cat2', name: 'Happyness & Values', slug: 'happyness-values', icon: 'heart-o' }), - f.create('Category', { id: 'cat3', name: 'Health & Wellbeing', slug: 'health-wellbeing', icon: 'medkit' }), - f.create('Category', { id: 'cat4', name: 'Environment & Nature', slug: 'environment-nature', icon: 'tree' }), - f.create('Category', { id: 'cat5', name: 'Animal Protection', slug: 'animalprotection', icon: 'paw' }), - f.create('Category', { id: 'cat6', name: 'Humanrights Justice', slug: 'humanrights-justice', icon: 'balance-scale' }), - f.create('Category', { id: 'cat7', name: 'Education & Sciences', slug: 'education-sciences', icon: 'graduation-cap' }), - f.create('Category', { id: 'cat8', name: 'Cooperation & Development', slug: 'cooperation-development', icon: 'users' }), - f.create('Category', { id: 'cat9', name: 'Democracy & Politics', slug: 'democracy-politics', icon: 'university' }), - f.create('Category', { id: 'cat10', name: 'Economy & Finances', slug: 'economy-finances', icon: 'money' }), - f.create('Category', { id: 'cat11', name: 'Energy & Technology', slug: 'energy-technology', icon: 'flash' }), - f.create('Category', { id: 'cat12', name: 'IT, Internet & Data Privacy', slug: 'it-internet-dataprivacy', icon: 'mouse-pointer' }), - f.create('Category', { id: 'cat13', name: 'Art, Curlure & Sport', slug: 'art-culture-sport', icon: 'paint-brush' }), - f.create('Category', { id: 'cat14', name: 'Freedom of Speech', slug: 'freedomofspeech', icon: 'bullhorn' }), - f.create('Category', { id: 'cat15', name: 'Consumption & Sustainability', slug: 'consumption-sustainability', icon: 'shopping-cart' }), - f.create('Category', { id: 'cat16', name: 'Global Peace & Nonviolence', slug: 'globalpeace-nonviolence', icon: 'angellist' }) + f.create('Category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }), + f.create('Category', { + id: 'cat2', + name: 'Happyness & Values', + slug: 'happyness-values', + icon: 'heart-o', + }), + f.create('Category', { + id: 'cat3', + name: 'Health & Wellbeing', + slug: 'health-wellbeing', + icon: 'medkit', + }), + f.create('Category', { + id: 'cat4', + name: 'Environment & Nature', + slug: 'environment-nature', + icon: 'tree', + }), + f.create('Category', { + id: 'cat5', + name: 'Animal Protection', + slug: 'animalprotection', + icon: 'paw', + }), + f.create('Category', { + id: 'cat6', + name: 'Humanrights Justice', + slug: 'humanrights-justice', + icon: 'balance-scale', + }), + f.create('Category', { + id: 'cat7', + name: 'Education & Sciences', + slug: 'education-sciences', + icon: 'graduation-cap', + }), + f.create('Category', { + id: 'cat8', + name: 'Cooperation & Development', + slug: 'cooperation-development', + icon: 'users', + }), + f.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + slug: 'democracy-politics', + icon: 'university', + }), + f.create('Category', { + id: 'cat10', + name: 'Economy & Finances', + slug: 'economy-finances', + icon: 'money', + }), + f.create('Category', { + id: 'cat11', + name: 'Energy & Technology', + slug: 'energy-technology', + icon: 'flash', + }), + f.create('Category', { + id: 'cat12', + name: 'IT, Internet & Data Privacy', + slug: 'it-internet-dataprivacy', + icon: 'mouse-pointer', + }), + f.create('Category', { + id: 'cat13', + name: 'Art, Curlure & Sport', + slug: 'art-culture-sport', + icon: 'paint-brush', + }), + f.create('Category', { + id: 'cat14', + name: 'Freedom of Speech', + slug: 'freedomofspeech', + icon: 'bullhorn', + }), + f.create('Category', { + id: 'cat15', + name: 'Consumption & Sustainability', + slug: 'consumption-sustainability', + icon: 'shopping-cart', + }), + f.create('Category', { + id: 'cat16', + name: 'Global Peace & Nonviolence', + slug: 'globalpeace-nonviolence', + icon: 'angellist', + }), ]) await Promise.all([ f.create('Tag', { id: 't1', name: 'Umwelt' }), f.create('Tag', { id: 't2', name: 'Naturschutz' }), f.create('Tag', { id: 't3', name: 'Demokratie' }), - f.create('Tag', { id: 't4', name: 'Freiheit' }) + f.create('Tag', { id: 't4', name: 'Freiheit' }), ]) const mention1 = 'Hey @jenny-rostock, what\'s up?' - const mention2 = 'Hey @jenny-rostock, here is another notification for you!' + const mention2 = + 'Hey @jenny-rostock, here is another notification for you!' await Promise.all([ - asAdmin.create('Post', { id: 'p0' }), + asAdmin.create('Post', { id: 'p0' }), asModerator.create('Post', { id: 'p1' }), - asUser.create('Post', { id: 'p2' }), - asTick.create('Post', { id: 'p3' }), - asTrick.create('Post', { id: 'p4' }), - asTrack.create('Post', { id: 'p5' }), - asAdmin.create('Post', { id: 'p6' }), + asUser.create('Post', { id: 'p2' }), + asTick.create('Post', { id: 'p3' }), + asTrick.create('Post', { id: 'p4' }), + asTrack.create('Post', { id: 'p5' }), + asAdmin.create('Post', { id: 'p6' }), asModerator.create('Post', { id: 'p7', content: `${mention1} ${faker.lorem.paragraph()}` }), - asUser.create('Post', { id: 'p8' }), - asTick.create('Post', { id: 'p9' }), - asTrick.create('Post', { id: 'p10' }), - asTrack.create('Post', { id: 'p11' }), - asAdmin.create('Post', { id: 'p12', content: `${mention2} ${faker.lorem.paragraph()}` }), + asUser.create('Post', { id: 'p8' }), + asTick.create('Post', { id: 'p9' }), + asTrick.create('Post', { id: 'p10' }), + asTrack.create('Post', { id: 'p11' }), + asAdmin.create('Post', { id: 'p12', content: `${mention2} ${faker.lorem.paragraph()}` }), asModerator.create('Post', { id: 'p13' }), - asUser.create('Post', { id: 'p14' }), - asTick.create('Post', { id: 'p15' }) + asUser.create('Post', { id: 'p14' }), + asTick.create('Post', { id: 'p15' }), ]) await Promise.all([ - f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }), - f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }), - f.relate('Post', 'Categories', { from: 'p2', to: 'cat2' }), - f.relate('Post', 'Categories', { from: 'p3', to: 'cat3' }), - f.relate('Post', 'Categories', { from: 'p4', to: 'cat4' }), - f.relate('Post', 'Categories', { from: 'p5', to: 'cat5' }), - f.relate('Post', 'Categories', { from: 'p6', to: 'cat6' }), - f.relate('Post', 'Categories', { from: 'p7', to: 'cat7' }), - f.relate('Post', 'Categories', { from: 'p8', to: 'cat8' }), - f.relate('Post', 'Categories', { from: 'p9', to: 'cat9' }), + f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }), + f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }), + f.relate('Post', 'Categories', { from: 'p2', to: 'cat2' }), + f.relate('Post', 'Categories', { from: 'p3', to: 'cat3' }), + f.relate('Post', 'Categories', { from: 'p4', to: 'cat4' }), + f.relate('Post', 'Categories', { from: 'p5', to: 'cat5' }), + f.relate('Post', 'Categories', { from: 'p6', to: 'cat6' }), + f.relate('Post', 'Categories', { from: 'p7', to: 'cat7' }), + f.relate('Post', 'Categories', { from: 'p8', to: 'cat8' }), + f.relate('Post', 'Categories', { from: 'p9', to: 'cat9' }), f.relate('Post', 'Categories', { from: 'p10', to: 'cat10' }), f.relate('Post', 'Categories', { from: 'p11', to: 'cat11' }), f.relate('Post', 'Categories', { from: 'p12', to: 'cat12' }), @@ -129,63 +250,45 @@ import Factory from './factories' f.relate('Post', 'Categories', { from: 'p14', to: 'cat14' }), f.relate('Post', 'Categories', { from: 'p15', to: 'cat15' }), - f.relate('Post', 'Tags', { from: 'p0', to: 't4' }), - f.relate('Post', 'Tags', { from: 'p1', to: 't1' }), - f.relate('Post', 'Tags', { from: 'p2', to: 't2' }), - f.relate('Post', 'Tags', { from: 'p3', to: 't3' }), - f.relate('Post', 'Tags', { from: 'p4', to: 't4' }), - f.relate('Post', 'Tags', { from: 'p5', to: 't1' }), - f.relate('Post', 'Tags', { from: 'p6', to: 't2' }), - f.relate('Post', 'Tags', { from: 'p7', to: 't3' }), - f.relate('Post', 'Tags', { from: 'p8', to: 't4' }), - f.relate('Post', 'Tags', { from: 'p9', to: 't1' }), + f.relate('Post', 'Tags', { from: 'p0', to: 't4' }), + f.relate('Post', 'Tags', { from: 'p1', to: 't1' }), + f.relate('Post', 'Tags', { from: 'p2', to: 't2' }), + f.relate('Post', 'Tags', { from: 'p3', to: 't3' }), + f.relate('Post', 'Tags', { from: 'p4', to: 't4' }), + f.relate('Post', 'Tags', { from: 'p5', to: 't1' }), + f.relate('Post', 'Tags', { from: 'p6', to: 't2' }), + f.relate('Post', 'Tags', { from: 'p7', to: 't3' }), + f.relate('Post', 'Tags', { from: 'p8', to: 't4' }), + f.relate('Post', 'Tags', { from: 'p9', to: 't1' }), f.relate('Post', 'Tags', { from: 'p10', to: 't2' }), f.relate('Post', 'Tags', { from: 'p11', to: 't3' }), f.relate('Post', 'Tags', { from: 'p12', to: 't4' }), f.relate('Post', 'Tags', { from: 'p13', to: 't1' }), f.relate('Post', 'Tags', { from: 'p14', to: 't2' }), - f.relate('Post', 'Tags', { from: 'p15', to: 't3' }) + f.relate('Post', 'Tags', { from: 'p15', to: 't3' }), ]) await Promise.all([ - asAdmin - .shout({ id: 'p2', type: 'Post' }), - asAdmin - .shout({ id: 'p6', type: 'Post' }), - asModerator - .shout({ id: 'p0', type: 'Post' }), - asModerator - .shout({ id: 'p6', type: 'Post' }), - asUser - .shout({ id: 'p6', type: 'Post' }), - asUser - .shout({ id: 'p7', type: 'Post' }), - asTick - .shout({ id: 'p8', type: 'Post' }), - asTick - .shout({ id: 'p9', type: 'Post' }), - asTrack - .shout({ id: 'p10', type: 'Post' }) + asAdmin.shout({ id: 'p2', type: 'Post' }), + asAdmin.shout({ id: 'p6', type: 'Post' }), + asModerator.shout({ id: 'p0', type: 'Post' }), + asModerator.shout({ id: 'p6', type: 'Post' }), + asUser.shout({ id: 'p6', type: 'Post' }), + asUser.shout({ id: 'p7', type: 'Post' }), + asTick.shout({ id: 'p8', type: 'Post' }), + asTick.shout({ id: 'p9', type: 'Post' }), + asTrack.shout({ id: 'p10', type: 'Post' }), ]) await Promise.all([ - asAdmin - .shout({ id: 'p2', type: 'Post' }), - asAdmin - .shout({ id: 'p6', type: 'Post' }), - asModerator - .shout({ id: 'p0', type: 'Post' }), - asModerator - .shout({ id: 'p6', type: 'Post' }), - asUser - .shout({ id: 'p6', type: 'Post' }), - asUser - .shout({ id: 'p7', type: 'Post' }), - asTick - .shout({ id: 'p8', type: 'Post' }), - asTick - .shout({ id: 'p9', type: 'Post' }), - asTrack - .shout({ id: 'p10', type: 'Post' }) + asAdmin.shout({ id: 'p2', type: 'Post' }), + asAdmin.shout({ id: 'p6', type: 'Post' }), + asModerator.shout({ id: 'p0', type: 'Post' }), + asModerator.shout({ id: 'p6', type: 'Post' }), + asUser.shout({ id: 'p6', type: 'Post' }), + asUser.shout({ id: 'p7', type: 'Post' }), + asTick.shout({ id: 'p8', type: 'Post' }), + asTick.shout({ id: 'p9', type: 'Post' }), + asTrack.shout({ id: 'p10', type: 'Post' }), ]) await Promise.all([ @@ -200,33 +303,49 @@ import Factory from './factories' asTrick.create('Comment', { id: 'c9', postId: 'p15' }), asTrack.create('Comment', { id: 'c10', postId: 'p15' }), asUser.create('Comment', { id: 'c11', postId: 'p15' }), - asUser.create('Comment', { id: 'c12', postId: 'p15' }) + asUser.create('Comment', { id: 'c12', postId: 'p15' }), ]) const disableMutation = 'mutation($id: ID!) { disable(id: $id) }' await Promise.all([ asModerator.mutate(disableMutation, { id: 'p11' }), - asModerator.mutate(disableMutation, { id: 'c5' }) + asModerator.mutate(disableMutation, { id: 'c5' }), ]) await Promise.all([ - asTick.create('Report', { description: 'I don\'t like this comment', id: 'c1' }), - asTrick.create('Report', { description: 'I don\'t like this post', id: 'p1' }), - asTrack.create('Report', { description: 'I don\'t like this user', id: 'u1' }) + asTick.create('Report', { description: "I don't like this comment", id: 'c1' }), + asTrick.create('Report', { description: "I don't like this post", id: 'p1' }), + asTrack.create('Report', { description: "I don't like this user", id: 'u1' }), ]) await Promise.all([ - f.create('Organization', { id: 'o1', name: 'Democracy Deutschland', description: 'Description for democracy-deutschland.' }), - f.create('Organization', { id: 'o2', name: 'Human-Connection', description: 'Description for human-connection.' }), - f.create('Organization', { id: 'o3', name: 'Pro Veg', description: 'Description for pro-veg.' }), - f.create('Organization', { id: 'o4', name: 'Greenpeace', description: 'Description for greenpeace.' }) + f.create('Organization', { + id: 'o1', + name: 'Democracy Deutschland', + description: 'Description for democracy-deutschland.', + }), + f.create('Organization', { + id: 'o2', + name: 'Human-Connection', + description: 'Description for human-connection.', + }), + f.create('Organization', { + id: 'o3', + name: 'Pro Veg', + description: 'Description for pro-veg.', + }), + f.create('Organization', { + id: 'o4', + name: 'Greenpeace', + description: 'Description for greenpeace.', + }), ]) await Promise.all([ f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o1' }), f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o2' }), - f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o2' }), - f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o3' }) + f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o2' }), + f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o3' }), ]) /* eslint-disable-next-line no-console */ console.log('Seeded Data...') diff --git a/backend/src/seed/seed-helpers.js b/backend/src/seed/seed-helpers.js index 23bde40ae..399d06670 100644 --- a/backend/src/seed/seed-helpers.js +++ b/backend/src/seed/seed-helpers.js @@ -19,7 +19,7 @@ const unsplashTopics = [ 'face', 'people', 'portrait', - 'amazing' + 'amazing', ] let unsplashTopicsTmp = [] @@ -30,7 +30,7 @@ const ngoLogos = [ 'https://dcassetcdn.com/design_img/10133/25833/25833_303600_10133_image.jpg', 'https://cdn.tutsplus.com/vector/uploads/legacy/articles/08bad_ngologos/20.jpg', 'https://cdn.tutsplus.com/vector/uploads/legacy/articles/08bad_ngologos/33.jpg', - null + null, ] const difficulties = ['easy', 'medium', 'hard'] @@ -38,8 +38,7 @@ const difficulties = ['easy', 'medium', 'hard'] export default { randomItem: (items, filter) => { let ids = filter - ? Object.keys(items) - .filter(id => { + ? Object.keys(items).filter(id => { return filter(items[id]) }) : _.keys(items) @@ -61,7 +60,7 @@ export default { } return res }, - random: (items) => { + random: items => { return _.shuffle(items).pop() }, randomDifficulty: () => { @@ -78,7 +77,9 @@ export default { if (unsplashTopicsTmp.length < 2) { unsplashTopicsTmp = _.shuffle(unsplashTopics) } - return 'https://source.unsplash.com/daily?' + unsplashTopicsTmp.pop() + ',' + unsplashTopicsTmp.pop() + return ( + 'https://source.unsplash.com/daily?' + unsplashTopicsTmp.pop() + ',' + unsplashTopicsTmp.pop() + ) }, randomCategories: (seederstore, allowEmpty = false) => { let count = Math.round(Math.random() * 3) @@ -101,8 +102,8 @@ export default { zipCode: faker.address.zipCode(), street: faker.address.streetAddress(), country: faker.address.countryCode(), - lat: 54.032726 - (Math.random() * 10), - lng: 6.558838 + (Math.random() * 10) + lat: 54.032726 - Math.random() * 10, + lng: 6.558838 + Math.random() * 10, }) } return addresses @@ -129,5 +130,5 @@ export default { code += chars.substr(n, 1) } return code - } + }, } diff --git a/backend/src/server.js b/backend/src/server.js index fe0d4ee1d..59261302f 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -28,17 +28,17 @@ let schema = makeAugmentedSchema({ resolvers, config: { query: { - exclude: ['Notfication', 'Statistics', 'LoggedInUser'] + exclude: ['Notfication', 'Statistics', 'LoggedInUser'], }, mutation: { - exclude: ['Notfication', 'Statistics', 'LoggedInUser'] + exclude: ['Notfication', 'Statistics', 'LoggedInUser'], }, - debug: debug - } + debug: debug, + }, }) schema = applyScalars(applyDirectives(schema)) -const createServer = (options) => { +const createServer = options => { const defaults = { context: async ({ request }) => { const authorizationHeader = request.headers.authorization || '' @@ -48,15 +48,15 @@ const createServer = (options) => { user, req: request, cypherParams: { - currentUserId: user ? user.id : null - } + currentUserId: user ? user.id : null, + }, } }, schema: schema, debug: debug, tracing: debug, middlewares: middleware(schema), - mocks: (process.env.MOCK === 'true') ? mocks : false + mocks: process.env.MOCK === 'true' ? mocks : false, } const server = new GraphQLServer(Object.assign({}, defaults, options))