Some refactoring + add timeout functions to some step definitions + add objectId to Post and schema

This commit is contained in:
Armin 2019-03-12 22:50:25 +01:00
parent 5aeb2845db
commit 00ba891cbf
9 changed files with 82 additions and 49 deletions

View File

@ -14,6 +14,7 @@ import NitroDataSource from './NitroDataSource'
import router from './routes'
import dotenv from 'dotenv'
import Collections from './Collections'
import uuid from 'uuid/v4'
const debug = require('debug')('ea')
let activityPub = null
@ -27,6 +28,7 @@ export default class ActivityPub {
this.dataSource = new NitroDataSource(uri)
this.collections = new Collections(this.dataSource)
}
static init (server) {
if (!activityPub) {
dotenv.config()
@ -179,30 +181,37 @@ export default class ActivityPub {
})
}
generateStatusId (slug) {
return `http://${this.domain}/activitypub/users/${slug}/status/${uuid()}`
}
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((el) => {
return this.trySend(activity, fromName, new URL(el).host, el)
sharedInboxEndpoints.map((sharedInbox) => {
return this.trySend(activity, fromName, new URL(sharedInbox).host, sharedInbox)
})
activity.to = activity.to.filter((el) => {
return !(isPublicAddressed({ to: el }))
activity.to = activity.to.filter((recipient) => {
return !(isPublicAddressed({ to: recipient }))
})
// serve the rest
activity.to.map(async (el) => {
const actorObject = await this.getActorObject(el)
return this.trySend(activity, fromName, new URL(el).host, actorObject.inbox)
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)
})
} else if (typeof activity.to === 'string') {
debug('is string')
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 (el) => {
const actorObject = await this.getActorObject(el)
return this.trySend(activity, fromName, new URL(el).host, actorObject.inbox)
activity.to.map(async (recipient) => {
const actorObject = await this.getActorObject(recipient)
return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox)
})
}
}

View File

@ -19,8 +19,6 @@ import { setContext } from 'apollo-link-context'
import { InMemoryCache } from 'apollo-cache-inmemory'
import fetch from 'node-fetch'
import { ApolloClient } from 'apollo-client'
import uuid from 'uuid'
import encode from '../jwt/encode'
import trunc from 'trunc-html'
const debug = require('debug')('ea:nitro-datasource')
@ -37,12 +35,12 @@ export default class NitroDataSource {
const cache = new InMemoryCache()
const authLink = setContext((_, { headers }) => {
// generate the authentication token (maybe from env? Which user?)
const token = encode({ name: 'ActivityPub', id: uuid() })
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}` : ''
}
}
})
@ -213,6 +211,7 @@ export default class NitroDataSource {
async getOutboxCollectionPage (actorId) {
const slug = extractNameFromId(actorId)
debug(`inside getting outbox collection page => ${slug}`)
const result = await this.client.query({
query: gql`
query {
@ -220,11 +219,16 @@ export default class NitroDataSource {
actorId
contributions {
id
activityId
objectId
title
slug
content
contentExcerpt
createdAt
author {
name
}
}
}
}
@ -240,7 +244,7 @@ export default class NitroDataSource {
outboxCollection.totalItems = posts.length
await Promise.all(
posts.map(async (post) => {
outboxCollection.orderedItems.push(await createArticleObject(post.activityId, post.objectId, post.content, extractNameFromId(post.id), post.id, post.createdAt))
outboxCollection.orderedItems.push(await createArticleObject(post.activityId, post.objectId, post.content, post.author.name, post.id, post.createdAt))
})
)
@ -332,6 +336,7 @@ export default class NitroDataSource {
}
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 {
@ -346,10 +351,16 @@ export default class NitroDataSource {
// ensure user and add author to post
const userId = await this.ensureUser(postObject.attributedTo)
debug(`userId = ${userId}`)
debug(`postId = ${postId}`)
result = await this.client.mutate({
mutation: gql`
mutation {
AddPostAuthor(from: {id: "${userId}"}, to: {id: "${postId}"})
AddPostAuthor(from: {id: "${userId}"}, to: {id: "${postId}"}) {
from {
name
}
}
}
`
})
@ -523,10 +534,11 @@ 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 result = await this.client.mutate({
mutation: gql`
mutation {
CreateUser(password: "${pw}", slug:"${name}", actorId: "${actorId}", name: "${name}") {
CreateUser(password: "${pw}", slug:"${slug}", actorId: "${actorId}", name: "${name}", email: "${slug}@test.org") {
id
}
}

View File

@ -13,36 +13,36 @@ router.post('/', async function (req, res, next) {
debug(`Request headers = ${JSON.stringify(req.headers, null, 2)}`)
switch (req.body.type) {
case 'Create':
if (req.body.send) {
await activityPub.sendActivity(req.body).catch(next)
break
}
await activityPub.handleCreateActivity(req.body).catch(next)
break
case 'Undo':
await activityPub.handleUndoActivity(req.body).catch(next)
break
case 'Follow':
debug('handleFollow')
await activityPub.handleFollowActivity(req.body)
debug('handledFollow')
await activityPub.handleFollowActivity(req.body).catch(next)
break
case 'Delete':
await activityPub.handleDeleteActivity(req.body).catch(next)
break
/* eslint-disable */
/* eslint-disable */
case 'Update':
await activityPub.handleUpdateActivity(req.body).catch(next)
break
case 'Accept':
await activityPub.handleAcceptActivity(req.body).catch(next)
case 'Reject':
// Do nothing
break
case 'Add':
break
case 'Remove':
break
case 'Like':
await activityPub.handleLikeActivity(req.body).catch(next)
break
case 'Dislike':
await activityPub.handleDislikeActivity(req.body).catch(next)
break
case 'Announce':
debug('else!!')
debug(JSON.stringify(req.body, null, 2))

View File

@ -96,6 +96,12 @@ export function isPublicAddressed (postObject) {
if (typeof postObject.to === 'string') {
postObject.to = [postObject.to]
}
if (typeof postObject === 'string') {
postObject.to = [postObject]
}
if (Array.isArray(postObject)) {
postObject.to = postObject
}
return postObject.to.includes('Public') ||
postObject.to.includes('as:Public') ||
postObject.to.includes('https://www.w3.org/ns/activitystreams#Public')

View File

@ -1,16 +1,15 @@
import createOrUpdateLocations from './nodes/locations'
import { generateRsaKeyPair } from '../activitypub/security'
import dotenv from 'dotenv'
import { resolve } from 'path'
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
dotenv.config()
export default {
Mutation: {
CreateUser: async (resolve, root, args, context, info) => {
const keys = generateRsaKeyPair()
Object.assign(args, keys)
args.actorId = `${process.env.ACTIVITYPUB_URI}/activitypub/users/${args.slug}`
args.actorId = `${process.env.GRAPHQL_URI}/activitypub/users/${args.slug}`
const result = await resolve(root, args, context, info)
await createOrUpdateLocations(args.id, args.locationName, context.driver)
return result

View File

@ -1,22 +1,23 @@
import { neo4jgraphql } from 'neo4j-graphql-js'
import { activityPub } from '../activitypub/ActivityPub'
import uuid from 'uuid/v4'
import as from 'activitystrea.ms'
import dotenv from 'dotenv'
/*
import as from 'activitystrea.ms'
import request from 'request'
*/
const debug = require('debug')('backend:schema')
dotenv.config()
export default {
Mutation: {
CreatePost: async (object, params, context, resolveInfo) => {
params.activityId = uuid()
params.activityId = activityPub.generateStatusId(context.user.slug)
params.objectId = activityPub.generateStatusId(context.user.slug)
const result = await neo4jgraphql(object, params, context, resolveInfo, false)
const session = context.driver.session()
const author = await session.run(
'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' +
'MERGE (post)<-[:WROTE]-(author) ' +
@ -25,6 +26,7 @@ export default {
postId: result.id
}
)
session.close()
debug(`actorId = ${author.records[0]._fields[0].properties.actorId}`)
if (Array.isArray(author.records) && author.records.length > 0) {
@ -54,10 +56,9 @@ export default {
try {
await activityPub.sendActivity(createActivity)
} catch (e) {
debug(`error sending post activity = ${JSON.stringify(e, null, 2)}`)
debug(`error sending post activity\n${e}`)
}
}
session.close()
return result
}

View File

@ -158,6 +158,7 @@ type User {
type Post {
id: ID!
activityId: String
objectId: String
author: User @relation(name: "WROTE", direction: "IN")
title: String!
slug: String

View File

@ -14,7 +14,7 @@ Feature: Delete an object
"type": "Create",
"actor": "https://aronda.org/users/bernd-das-brot",
"object": {
"id": "https://aronda.org/users/bernd-das-brot/status/kljsdfg9843jknsdf",
"id": "https://aronda.org/users/bernd-das-brot/status/kljsdfg9843jknsdf234",
"type": "Article",
"published": "2019-02-07T19:37:55.002Z",
"attributedTo": "https://aronda.org/users/bernd-das-brot",
@ -29,13 +29,13 @@ Feature: Delete an object
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/users/karl-heinz/status/a4DJ2afdg323v32641vna42lkj685kasd2",
"id": "https://localhost:4123/activitypub/users/karl-heinz/status/a4DJ2afdg323v32641vna42lkj685kasd2",
"type": "Delete",
"object": {
"id": "https://aronda.org/users/bernd-das-brot/status/kljsdfg9843jknsdf",
"id": "https://aronda.org/activitypub/users/bernd-das-brot/status/kljsdfg9843jknsdf234",
"type": "Article",
"published": "2019-02-07T19:37:55.002Z",
"attributedTo": "https://aronda.org/users/bernd-das-brot",
"attributedTo": "https://aronda.org/activitypub/users/bernd-das-brot",
"content": "Hi Max, how are you?",
"to": "https://localhost:4123/activitypub/users/moritz"
}
@ -45,10 +45,10 @@ Feature: Delete an object
And the object is removed from the outbox collection of "bernd-das-brot"
"""
{
"id": "https://aronda.org/users/bernd-das-brot/status/kljsdfg9843jknsdf",
"id": "https://aronda.org/activitypub/users/bernd-das-brot/status/kljsdfg9843jknsdf234",
"type": "Article",
"published": "2019-02-07T19:37:55.002Z",
"attributedTo": "https://aronda.org/users/bernd-das-brot",
"attributedTo": "https://aronda.org/activitypub/users/bernd-das-brot",
"content": "Hi Max, how are you?",
"to": "https://localhost:4123/activitypub/users/moritz"
}

View File

@ -105,14 +105,17 @@ Then('the activity is added to the users inbox collection', async function () {
})
Then('the post with id {string} to be created', async function (id) {
const result = await client.request(`
setTimeout(async () => {
const result = await client.request(`
query {
Post(id: "${id}") {
title
}
}
`)
expect(result.data.Post).to.be.an('array').that.is.not.empty // eslint-disable-line
expect(result.data.Post).to.be.an('array').that.is.not.empty // eslint-disable-line
}, 2000)
})
Then('the object is removed from the outbox collection of {string}', async function (name, object) {
@ -126,7 +129,8 @@ Then('I send a GET request to {string} and expect a ordered collection', () => {
})
Then('the post with id {string} has been liked by {string}', async function (id, slug) {
const result = await client.request(`
setTimeout(async () => {
const result = await client.request(`
query {
Post(id: "${id}") {
shoutedBy {
@ -135,6 +139,7 @@ Then('the post with id {string} has been liked by {string}', async function (id,
}
}
`)
expect(result.data.Post[0].shoutedBy).to.be.an('array').that.is.not.empty // eslint-disable-line
expect(result.data.Post[0].shoutedBy[0].slug).to.equal(slug)
expect(result.data.Post[0].shoutedBy).to.be.an('array').that.is.not.empty // eslint-disable-line
expect(result.data.Post[0].shoutedBy[0].slug).to.equal(slug)
}, 2000)
})