You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
8.1 KiB

// hive.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const mongoose = require('mongoose');
const UserSubscription = mongoose.model('UserSubscription');
const KaleidoscopeEvent = mongoose.model('KaleidoscopeEvent');
const slug = require('slug');
const striptags = require('striptags');
const { SiteService, SiteError } = require('../../lib/site-lib');
class HiveService extends SiteService {
constructor (dtp) {
super(dtp, module.exports);
}
async subscribe (user, client, emitterId) {
await UserSubscription.updateOne(
{ user: user._id },
{
$addToSet: {
subscriptions: {
client: client._id,
emitterId,
},
},
},
{
upsert: true,
},
);
}
async unsubscribe (user, subscription) {
await UserSubscription.updateOne(
{ user: user._id },
{ $pull: { subscriptions: subscription } },
);
}
extractHashtags (content) {
const hashtags = content
.split(/ \r\n/g)
.filter((tag) => tag[0] === '#')
.map((tag) => slug(tag.slice(1)))
;
return hashtags;
}
extractLinks (content) {
let links = content
.split(/( |\r|\n)/g)
.filter((tag) => {
const test = tag.trim().toLowerCase();
return test.startsWith('http://') || test.startsWith('https://');
});
return links;
}
async resolveLink (author, url) {
const jobData = {
authorType: author.type,
author: author._id,
url,
};
this.log.info('creating job to resolve link', { jobData });
await this.resolver.add('resolve-link', jobData);
}
async processKaleidoscopeEvent (eventDefinition) {
const {
userNotification: userNotificationService,
oauth2: oauth2Service,
} = this.dtp.services;
const client = await oauth2Service.getClientByDomainKey(eventDefinition.source.site.domainKey);
if (!client) {
throw new SiteError(403, 'Unknown client domain key');
}
const event = await this.createKaleidoscopeEvent(eventDefinition);
await UserSubscription
.find({
'subscriptions.client': client._id,
'subscriptions.emitterId': eventDefinition.source.emitter._id,
})
.select('-subscriptions')
.cursor()
.eachAsync(async (subscription) => {
await userNotificationService.create(subscription.user, event);
}, 3);
this.emit('kaleidoscope:event', event, client);
}
async createKaleidoscopeEvent (eventDefinition) {
const NOW = new Date();
/*
* Validate general notification data
*/
if (!eventDefinition.action) {
throw new SiteError(406, 'Missing action');
}
if (!eventDefinition.content) {
throw new SiteError(406, 'Missing content');
}
if (!eventDefinition.href) {
throw new SiteError(406, 'Missing href');
}
/*
* Validate source data
*/
if (!eventDefinition.source) {
throw new SiteError(406, 'Missing source information');
}
/*
* Validate source site
*/
if (!eventDefinition.source.site) {
throw new SiteError(406, 'Missing source site information');
}
if (!eventDefinition.source.site.name) {
throw new SiteError(406, 'Missing source site name');
}
if (!eventDefinition.source.site.description) {
throw new SiteError(406, 'Missing source site description');
}
if (!eventDefinition.source.site.domain) {
throw new SiteError(406, 'Missing source site domain');
}
if (!eventDefinition.source.site.domainKey) {
throw new SiteError(406, 'Missing source site domain key');
}
if (!eventDefinition.source.site.company) {
throw new SiteError(406, 'Missing source site company name');
}
if (!eventDefinition.source.site.coreAuth ||
!eventDefinition.source.site.coreAuth.scopes ||
!Array.isArray(eventDefinition.source.site.coreAuth.scopes) ||
(eventDefinition.source.site.coreAuth.scopes.length === 0)) {
throw new SiteError(406, 'Missing source site Core auth or scope information');
}
/*
* Validate source package
*/
if (!eventDefinition.source.pkg) {
throw new SiteError(406, 'Missing source package information');
}
if (!eventDefinition.source.pkg.name) {
throw new SiteError(406, 'Missing source package name');
}
if (!eventDefinition.source.pkg.version) {
throw new SiteError(406, 'Missing source package version');
}
/*
* Validate source emitter
*/
if (!eventDefinition.source.emitter) {
throw new SiteError(406, 'Missing source emitter information');
}
if (!eventDefinition.source.emitter.emitterId) {
throw new SiteError(406, 'Missing source emitter ID');
}
if (!eventDefinition.source.emitter.username) {
throw new SiteError(406, 'Missing source emitter username');
}
if (!eventDefinition.source.emitter.href) {
throw new SiteError(406, 'Missing source emitter href');
}
const event = new KaleidoscopeEvent();
event.created = NOW;
if (eventDefinition.recipientType && eventDefinition.recipient) {
event.recipientType = eventDefinition.recipientType;
event.recipient = mongoose.Types.ObjectId(eventDefinition.recipient);
}
event.action = striptags(eventDefinition.action.trim().toLowerCase());
if (eventDefinition.label) {
event.label = striptags(eventDefinition.label.trim());
}
event.content = striptags(eventDefinition.content.trim());
event.href = striptags(eventDefinition.href.trim());
if (eventDefinition.thumbnail) {
event.thumbnail = striptags(eventDefinition.thumbnail);
}
event.source = {
pkg: {
name: striptags(eventDefinition.source.pkg.name.trim().toLowerCase()),
version: striptags(eventDefinition.source.pkg.version.trim()),
},
site: {
name: striptags(eventDefinition.source.site.name.trim()),
domain: striptags(eventDefinition.source.site.domain.trim().toLowerCase()),
domainKey: striptags(eventDefinition.source.site.domainKey.trim().toLowerCase()),
coreAuth: {
scopes: eventDefinition.source.site.coreAuth.scopes.map((scope) => scope.trim().toLowerCase()),
},
},
};
if (eventDefinition.source.emitter) {
event.source.emitter = {
emitterType: striptags(eventDefinition.source.emitter.emitterType),
emitterId: mongoose.Types.ObjectId(eventDefinition.source.emitter.emitterId),
href: striptags(eventDefinition.source.emitter.href.trim()),
};
if (eventDefinition.source.emitter.displayName) {
event.source.emitter.displayName = striptags(eventDefinition.source.emitter.displayName.trim());
}
if (eventDefinition.source.emitter.username) {
event.source.emitter.username = striptags(eventDefinition.source.emitter.username.trim());
}
}
if (eventDefinition.source.site.company) {
event.source.site.company = striptags(eventDefinition.source.site.company.trim());
}
if (eventDefinition.source.site.description) {
event.source.site.description = striptags(eventDefinition.source.site.description.trim());
}
if (eventDefinition.source.emitter.displayName) {
event.source.emitter.displayName = striptags(eventDefinition.source.emitter.displayName);
}
event.attachmentType = eventDefinition.attachmentType;
event.attachment = eventDefinition.attachment;
await event.save();
return event.toObject();
}
async getConstellationTimeline (user, pagination) {
const totalEventCount = await KaleidoscopeEvent.estimatedDocumentCount();
const job = { };
if (user) {
job.search = {
$or: [
{ recipient: { $exists: false } },
{ recipient: user._id },
],
};
} else {
job.search = { recipient: { $exists: false } };
}
const events = await KaleidoscopeEvent
.find(job.search)
.sort({ created: -1 })
.skip(pagination.skip)
.limit(pagination.cpp)
.lean();
return { events, totalEventCount };
}
}
module.exports = {
slug: 'hive',
name: 'hive',
create: (dtp) => { return new HiveService(dtp); },
};