mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
576 lines
17 KiB
JavaScript
576 lines
17 KiB
JavaScript
import {
|
|
throwErrorIfApolloErrorOccurred,
|
|
extractIdFromActivityId,
|
|
extractNameFromId,
|
|
constructIdFromName,
|
|
} from './utils'
|
|
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'
|
|
import { setContext } from 'apollo-link-context'
|
|
import { InMemoryCache } from 'apollo-cache-inmemory'
|
|
import fetch from 'node-fetch'
|
|
import { ApolloClient } from 'apollo-client'
|
|
import trunc from 'trunc-html'
|
|
const debug = require('debug')('ea:datasource')
|
|
|
|
export default class NitroDataSource {
|
|
constructor(uri) {
|
|
this.uri = uri
|
|
const defaultOptions = {
|
|
query: {
|
|
fetchPolicy: 'network-only',
|
|
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'
|
|
// return the headers to the context so httpLink can read them
|
|
return {
|
|
headers: {
|
|
...headers,
|
|
Authorization: token ? `Bearer ${token}` : '',
|
|
},
|
|
}
|
|
})
|
|
this.client = new ApolloClient({
|
|
link: authLink.concat(link),
|
|
cache: cache,
|
|
defaultOptions,
|
|
})
|
|
}
|
|
|
|
async getFollowersCollection(actorId) {
|
|
const slug = extractNameFromId(actorId)
|
|
debug(`slug= ${slug}`)
|
|
const result = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
User(slug: "${slug}") {
|
|
followedByCount
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
debug('successfully fetched followers')
|
|
debug(result.data)
|
|
if (result.data) {
|
|
const actor = result.data.User[0]
|
|
const followersCount = actor.followedByCount
|
|
|
|
const followersCollection = createOrderedCollection(slug, 'followers')
|
|
followersCollection.totalItems = followersCount
|
|
|
|
return followersCollection
|
|
} else {
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
}
|
|
|
|
async getFollowersCollectionPage(actorId) {
|
|
const slug = extractNameFromId(actorId)
|
|
debug(`getFollowersPage slug = ${slug}`)
|
|
const result = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
User(slug:"${slug}") {
|
|
followedBy {
|
|
slug
|
|
}
|
|
followedByCount
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
debug(result.data)
|
|
if (result.data) {
|
|
const actor = result.data.User[0]
|
|
const followers = actor.followedBy
|
|
const followersCount = actor.followedByCount
|
|
|
|
const followersCollection = createOrderedCollectionPage(slug, 'followers')
|
|
followersCollection.totalItems = followersCount
|
|
debug(`followers = ${JSON.stringify(followers, null, 2)}`)
|
|
await Promise.all(
|
|
followers.map(async (follower) => {
|
|
followersCollection.orderedItems.push(constructIdFromName(follower.slug))
|
|
}),
|
|
)
|
|
|
|
return followersCollection
|
|
} else {
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
}
|
|
|
|
async getFollowingCollection(actorId) {
|
|
const slug = extractNameFromId(actorId)
|
|
const result = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
User(slug:"${slug}") {
|
|
followingCount
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
debug(result.data)
|
|
if (result.data) {
|
|
const actor = result.data.User[0]
|
|
const followingCount = actor.followingCount
|
|
|
|
const followingCollection = createOrderedCollection(slug, 'following')
|
|
followingCollection.totalItems = followingCount
|
|
|
|
return followingCollection
|
|
} else {
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
}
|
|
|
|
async getFollowingCollectionPage(actorId) {
|
|
const slug = extractNameFromId(actorId)
|
|
const result = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
User(slug:"${slug}") {
|
|
following {
|
|
slug
|
|
}
|
|
followingCount
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
debug(result.data)
|
|
if (result.data) {
|
|
const actor = result.data.User[0]
|
|
const following = actor.following
|
|
const followingCount = actor.followingCount
|
|
|
|
const followingCollection = createOrderedCollectionPage(slug, 'following')
|
|
followingCollection.totalItems = followingCount
|
|
|
|
await Promise.all(
|
|
following.map(async (user) => {
|
|
followingCollection.orderedItems.push(await constructIdFromName(user.slug))
|
|
}),
|
|
)
|
|
|
|
return followingCollection
|
|
} else {
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
}
|
|
|
|
async getOutboxCollection(actorId) {
|
|
const slug = extractNameFromId(actorId)
|
|
const result = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
User(slug:"${slug}") {
|
|
contributions {
|
|
title
|
|
slug
|
|
content
|
|
contentExcerpt
|
|
createdAt
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
debug(result.data)
|
|
if (result.data) {
|
|
const actor = result.data.User[0]
|
|
const posts = actor.contributions
|
|
|
|
const outboxCollection = createOrderedCollection(slug, 'outbox')
|
|
outboxCollection.totalItems = posts.length
|
|
|
|
return outboxCollection
|
|
} else {
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
}
|
|
|
|
async getOutboxCollectionPage(actorId) {
|
|
const slug = extractNameFromId(actorId)
|
|
debug(`inside getting outbox collection page => ${slug}`)
|
|
const result = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
User(slug:"${slug}") {
|
|
actorId
|
|
contributions {
|
|
id
|
|
activityId
|
|
objectId
|
|
title
|
|
slug
|
|
content
|
|
contentExcerpt
|
|
createdAt
|
|
author {
|
|
slug
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
debug(result.data)
|
|
if (result.data) {
|
|
const actor = result.data.User[0]
|
|
const posts = actor.contributions
|
|
|
|
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,
|
|
),
|
|
)
|
|
}),
|
|
)
|
|
|
|
debug('after createNote')
|
|
return outboxCollection
|
|
} else {
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
}
|
|
|
|
async undoFollowActivity(fromActorId, toActorId) {
|
|
const fromUserId = await this.ensureUser(fromActorId)
|
|
const toUserId = await this.ensureUser(toActorId)
|
|
const result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
RemoveUserFollowedBy(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
|
|
from { name }
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
debug(`undoFollowActivity result = ${JSON.stringify(result, null, 2)}`)
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
|
|
async saveFollowersCollectionPage(followersCollection, onlyNewestItem = true) {
|
|
debug('inside saveFollowers')
|
|
let orderedItems = followersCollection.orderedItems
|
|
const toUserName = extractNameFromId(followersCollection.id)
|
|
const toUserId = await this.ensureUser(constructIdFromName(toUserName))
|
|
orderedItems = onlyNewestItem ? [orderedItems.pop()] : orderedItems
|
|
|
|
return Promise.all(
|
|
orderedItems.map(async (follower) => {
|
|
debug(`follower = ${follower}`)
|
|
const fromUserId = await this.ensureUser(follower)
|
|
debug(`fromUserId = ${fromUserId}`)
|
|
debug(`toUserId = ${toUserId}`)
|
|
const result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
AddUserFollowedBy(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
|
|
from { name }
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
debug(`addUserFollowedBy edge = ${JSON.stringify(result, null, 2)}`)
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
debug('saveFollowers: added follow edge successfully')
|
|
}),
|
|
)
|
|
}
|
|
|
|
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) => {
|
|
debug(`follower = ${following}`)
|
|
const toUserId = await this.ensureUser(following)
|
|
debug(`fromUserId = ${fromUserId}`)
|
|
debug(`toUserId = ${toUserId}`)
|
|
const result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
AddUserFollowing(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
|
|
from { name }
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
debug(`addUserFollowing edge = ${JSON.stringify(result, null, 2)}`)
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
debug('saveFollowing: added follow edge successfully')
|
|
}),
|
|
)
|
|
}
|
|
|
|
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)',
|
|
)
|
|
}
|
|
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
|
|
}") {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
|
|
// 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}"}) {
|
|
from {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
|
|
async deletePost(activity) {
|
|
const result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
DeletePost(id: "${extractIdFromActivityId(activity.object.id)}") {
|
|
title
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
|
|
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}") {
|
|
title
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
|
|
async createShouted(activity) {
|
|
const userId = await this.ensureUser(activity.actor)
|
|
const postId = extractIdFromActivityId(activity.object)
|
|
const result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
AddUserShouted(from: {id: "${userId}"}, to: {id: "${postId}"}) {
|
|
from {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
if (!result.data.AddUserShouted) {
|
|
debug('something went wrong shouting post')
|
|
throw Error('User or Post not exists')
|
|
}
|
|
}
|
|
|
|
async deleteShouted(activity) {
|
|
const userId = await this.ensureUser(activity.actor)
|
|
const postId = extractIdFromActivityId(activity.object)
|
|
const result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
RemoveUserShouted(from: {id: "${userId}"}, to: {id: "${postId}"}) {
|
|
from {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
if (!result.data.AddUserShouted) {
|
|
debug('something went wrong disliking a post')
|
|
throw Error('User or Post not exists')
|
|
}
|
|
}
|
|
|
|
async getSharedInboxEndpoints() {
|
|
const result = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
SharedInboxEndpoint {
|
|
uri
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
return result.data.SharedInboxEnpoint
|
|
}
|
|
|
|
async addSharedInboxEndpoint(uri) {
|
|
try {
|
|
const result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
CreateSharedInboxEndpoint(uri: "${uri}")
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
return true
|
|
} catch (e) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
async createComment(activity) {
|
|
const postObject = activity.object
|
|
let result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
CreateComment(content: "${
|
|
postObject.content
|
|
}", activityId: "${extractIdFromActivityId(activity.id)}") {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
|
|
const toUserId = await this.ensureUser(activity.actor)
|
|
const result2 = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
AddCommentAuthor(from: {id: "${result.data.CreateComment.id}"}, to: {id: "${toUserId}"}) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result2)
|
|
|
|
const postId = extractIdFromActivityId(postObject.inReplyTo)
|
|
result = await this.client.mutate({
|
|
mutation: gql`
|
|
mutation {
|
|
AddCommentPost(from: { id: "${result.data.CreateComment.id}", to: { id: "${postId}" }}) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
}
|
|
|
|
/**
|
|
* This function will search for user existence and will create a disabled user with a random 16 bytes password when no user is found.
|
|
*
|
|
* @param actorId
|
|
* @returns {Promise<*>}
|
|
*/
|
|
async ensureUser(actorId) {
|
|
debug(`inside ensureUser = ${actorId}`)
|
|
const name = extractNameFromId(actorId)
|
|
const queryResult = await this.client.query({
|
|
query: gql`
|
|
query {
|
|
User(slug: "${name}") {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
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
|
|
} else {
|
|
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:"${slug}", actorId: "${actorId}", name: "${name}", email: "${slug}@test.org") {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
throwErrorIfApolloErrorOccurred(result)
|
|
|
|
return result.data.CreateUser.id
|
|
}
|
|
}
|
|
}
|