enigma-bbs/core/activitypub/collection.js

250 lines
7.2 KiB
JavaScript
Raw Normal View History

2023-01-20 05:31:14 +00:00
const { makeUserUrl } = require('./util');
const ActivityPubObject = require('./object');
const apDb = require('../database').dbs.activitypub;
const { getISOTimestampString } = require('../database');
const { Errors } = require('../enig_error.js');
2023-01-21 05:15:59 +00:00
// deps
2023-01-22 20:51:32 +00:00
const { isString, get, isObject } = require('lodash');
const APPublicCollectionId = 'https://www.w3.org/ns/activitystreams#Public';
const APPublicOwningUserId = 0;
module.exports = class Collection extends ActivityPubObject {
constructor(obj) {
super(obj);
}
2023-01-22 20:51:32 +00:00
static get PublicCollectionId() {
return APPublicCollectionId;
}
2023-01-21 05:15:59 +00:00
static followers(owningUser, page, webServer, cb) {
return Collection.getOrdered(
'followers',
owningUser,
false,
page,
e => e.id,
webServer,
cb
);
}
static following(owningUser, page, webServer, cb) {
return Collection.getOrdered(
'following',
owningUser,
false,
page,
e => get(e, 'object.id'),
webServer,
cb
);
}
static addFollower(owningUser, followingActor, cb) {
2023-01-21 08:19:19 +00:00
return Collection.addToCollection(
'followers',
owningUser,
followingActor.id,
followingActor,
2023-01-22 01:51:54 +00:00
false,
cb
);
}
static outbox(owningUser, page, webServer, cb) {
return Collection.getOrdered(
'outbox',
owningUser,
false,
page,
null,
webServer,
cb
);
}
2023-01-22 18:02:45 +00:00
static addOutboxItem(owningUser, outboxItem, isPrivate, cb) {
2023-01-22 01:51:54 +00:00
return Collection.addToCollection(
'outbox',
owningUser,
outboxItem.id,
outboxItem,
2023-01-22 18:02:45 +00:00
isPrivate,
2023-01-21 08:19:19 +00:00
cb
);
2023-01-21 05:15:59 +00:00
}
2023-01-22 20:51:32 +00:00
static addPublicInboxItem(inboxItem, cb) {
return Collection.addToCollection(
'publicInbox',
APPublicOwningUserId,
inboxItem.id,
inboxItem,
false,
cb
);
}
static embeddedObjById(collectionName, includePrivate, objectId, cb) {
const privateQuery = includePrivate ? '' : ' AND is_private = FALSE';
apDb.get(
`SELECT obj_json
FROM collection
WHERE name = ?
${privateQuery}
AND json_extract(obj_json, '$.object.id') = ?;`,
[collectionName, objectId],
(err, row) => {
if (err) {
return cb(err);
}
if (!row) {
return cb(
Errors.DoesNotExist(
`No embedded Object with object.id of "${objectId}" found`
)
);
}
const obj = ActivityPubObject.fromJsonString(row.obj_json);
if (!obj) {
return cb(Errors.Invalid('Failed to parse Object JSON'));
}
return cb(null, obj);
}
);
}
static getOrdered(
collectionName,
owningUser,
includePrivate,
page,
mapper,
webServer,
cb
) {
2023-01-22 01:51:54 +00:00
const privateQuery = includePrivate ? '' : ' AND is_private = FALSE';
2023-01-22 20:51:32 +00:00
const owningUserId = isObject(owningUser) ? owningUser.userId : owningUser;
2023-01-22 01:51:54 +00:00
// e.g. http://some.host/_enig/ap/collections/1234/followers
const collectionIdBase =
makeUserUrl(webServer, owningUser, `/ap/collections/${owningUserId}`) +
`/${collectionName}`;
if (!page) {
return apDb.get(
`SELECT COUNT(id) AS count
2023-01-21 08:19:19 +00:00
FROM collection
2023-01-22 01:51:54 +00:00
WHERE user_id = ? AND name = ?${privateQuery};`,
[owningUserId, collectionName],
(err, row) => {
if (err) {
return cb(err);
}
2023-01-22 01:51:54 +00:00
//
// Mastodon for instance, will never follow up for the
// actual data from some Collections such as 'followers';
// Instead, they only use the |totalItems| to form an
// approximate follower count.
//
2023-01-21 05:15:59 +00:00
let obj;
if (row.count > 0) {
obj = {
id: collectionIdBase,
2023-01-21 05:15:59 +00:00
type: 'OrderedCollection',
first: `${collectionIdBase}?page=1`,
2023-01-21 05:15:59 +00:00
totalItems: row.count,
};
} else {
obj = {
id: collectionIdBase,
2023-01-21 05:15:59 +00:00
type: 'OrderedCollection',
totalItems: 0,
orderedItems: [],
};
}
return cb(null, new Collection(obj));
}
);
}
// :TODO: actual paging...
apDb.all(
2023-01-21 08:19:19 +00:00
`SELECT obj_json
FROM collection
2023-01-22 01:51:54 +00:00
WHERE user_id = ? AND name = ?${privateQuery}
ORDER BY timestamp;`,
[owningUserId, collectionName],
(err, entries) => {
if (err) {
return cb(err);
}
2023-01-21 05:15:59 +00:00
entries = entries || [];
if (mapper && entries.length > 0) {
entries = entries.map(mapper);
}
const obj = {
id: `${collectionIdBase}/page=${page}`,
type: 'OrderedCollectionPage',
totalItems: entries.length,
orderedItems: entries,
partOf: collectionIdBase,
};
return cb(null, new Collection(obj));
}
);
}
static addToCollection(collectionName, owningUser, objectId, obj, isPrivate, cb) {
2023-01-21 08:19:19 +00:00
if (!isString(obj)) {
obj = JSON.stringify(obj);
}
2023-01-22 20:51:32 +00:00
const owningUserId = isObject(owningUser) ? owningUser.userId : owningUser;
2023-01-22 01:51:54 +00:00
isPrivate = isPrivate ? 1 : 0;
apDb.run(
2023-01-22 01:51:54 +00:00
`INSERT OR IGNORE INTO collection (name, timestamp, user_id, obj_id, obj_json, is_private)
VALUES (?, ?, ?, ?, ?, ?);`,
[
collectionName,
getISOTimestampString(),
owningUserId,
objectId,
obj,
isPrivate,
],
function res(err) {
// non-arrow for 'this' scope
2023-01-21 05:15:59 +00:00
if (err) {
return cb(err);
}
return cb(err, this.lastID);
}
);
}
2023-01-21 08:19:19 +00:00
static removeFromCollectionById(collectionName, owningUser, objectId, cb) {
2023-01-22 20:51:32 +00:00
const owningUserId = isObject(owningUser) ? owningUser.userId : owningUser;
2023-01-21 08:19:19 +00:00
apDb.run(
`DELETE FROM collection
WHERE user_id = ? AND name = ? AND obj_id = ?;`,
[owningUserId, collectionName, objectId],
2023-01-21 08:19:19 +00:00
err => {
return cb(err);
}
);
}
};