feat: category actors, stub outbox

This commit is contained in:
Julian Lam
2024-02-02 17:19:59 -05:00
parent ae042ce39e
commit 88733a5160
6 changed files with 142 additions and 48 deletions

View File

@@ -2,10 +2,13 @@
const nconf = require('nconf');
const mime = require('mime');
const path = require('path');
const user = require('../user');
const categories = require('../categories');
const posts = require('../posts');
const topics = require('../topics');
const utils = require('../utils');
const activitypub = module.parent.exports;
const Mocks = module.exports;
@@ -111,7 +114,9 @@ Mocks.post = async (objects) => {
return single ? posts.pop() : posts;
};
Mocks.actor = async (uid) => {
Mocks.actors = {};
Mocks.actors.user = async (uid) => {
let { username, userslug, displayname: name, aboutme, picture, 'cover:url': cover } = await user.getUserData(uid);
const publicKey = await activitypub.getPublicKey(uid);
@@ -157,6 +162,36 @@ Mocks.actor = async (uid) => {
};
};
Mocks.actors.category = async (cid) => {
let { name, slug, description: summary, backgroundImage } = await categories.getCategoryData(cid);
if (backgroundImage) {
const filename = utils.decodeHTMLEntities(backgroundImage).split('/').pop();
const imagePath = path.join(nconf.get('upload_path'), 'category', filename);
backgroundImage = {
type: 'Image',
mediaType: mime.getType(imagePath),
url: `${nconf.get('url')}${utils.decodeHTMLEntities(backgroundImage)}`,
};
}
return {
'@context': 'https://www.w3.org/ns/activitystreams',
id: `${nconf.get('url')}/category/${cid}`,
url: `${nconf.get('url')}/category/${slug}`,
// followers: ,
// following: ,
inbox: `${nconf.get('url')}/category/${cid}/inbox`,
outbox: `${nconf.get('url')}/category/${cid}/outbox`,
type: 'Group',
name,
preferredUsername: name,
summary,
icon: backgroundImage,
};
};
Mocks.note = async (post) => {
const id = `${nconf.get('url')}/post/${post.pid}`;
const published = new Date(parseInt(post.timestamp, 10)).toISOString();

View File

@@ -88,7 +88,7 @@ activitypubApi.update = {};
activitypubApi.update.profile = async (caller, { uid }) => {
const [object, followers] = await Promise.all([
activitypub.mocks.actor(uid),
activitypub.mocks.actors.user(uid),
db.getSortedSetMembers(`followersRemote:${caller.uid}`),
]);

View File

@@ -4,6 +4,7 @@ const nconf = require('nconf');
const meta = require('../../meta');
const posts = require('../../posts');
const categories = require('../../categories');
const activitypub = require('../../activitypub');
const Actors = module.exports;
@@ -33,7 +34,7 @@ Actors.application = async function (req, res) {
Actors.user = async function (req, res) {
// todo: view:users priv gate
const payload = await activitypub.mocks.actor(req.params.uid);
const payload = await activitypub.mocks.actors.user(req.params.uid);
res.status(200).json(payload);
};
@@ -56,3 +57,13 @@ Actors.note = async function (req, res, next) {
const payload = await activitypub.mocks.note(post);
res.status(200).json(payload);
};
Actors.category = async function (req, res, next) {
const exists = await categories.exists(req.params.cid);
if (!exists) {
return next('route');
}
const payload = await activitypub.mocks.actors.category(req.params.cid);
res.status(200).json(payload);
};

View File

@@ -90,6 +90,16 @@ Controller.getOutbox = async (req, res) => {
});
};
Controller.getCategoryOutbox = async (req, res) => {
// stub
res.status(200).json({
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'OrderedCollection',
totalItems: 0,
orderedItems: [],
});
};
Controller.postOutbox = async (req, res) => {
// This is a client-to-server feature so it is deliberately not implemented at this time.
res.sendStatus(405);

View File

@@ -3,6 +3,7 @@
const nconf = require('nconf');
const user = require('../user');
const categories = require('../categories');
const privileges = require('../privileges');
const Controller = module.exports;
@@ -15,53 +16,86 @@ Controller.webfinger = async (req, res) => {
return res.sendStatus(400);
}
const canView = await privileges.global.can('view:users', req.uid);
if (!canView) {
return res.sendStatus(403);
}
// Get the slug
const slug = resource.slice(5, resource.length - (host.length + 1));
let uid = await user.getUidByUserslug(slug);
if (slug === hostname) {
uid = 0;
} else if (!uid) {
return res.sendStatus(404);
}
const response = {
const uid = await user.getUidByUserslug(slug);
let response = {
subject: `acct:${slug}@${host}`,
};
if (uid) {
response.aliases = [
`${nconf.get('url')}/uid/${uid}`,
`${nconf.get('url')}/user/${slug}`,
];
try {
if (slug.startsWith('cid.')) {
response = await category(req.uid, slug.slice(4), response);
} else if (slug === hostname) {
response = application(response);
} else if (uid) {
response = await profile(req.uid, uid, response);
} else {
return res.sendStatus(404);
}
response.links = [
{
rel: 'self',
type: 'application/activity+json',
href: `${nconf.get('url')}/uid/${uid}`, // actor
},
{
rel: 'http://webfinger.net/rel/profile-page',
type: 'text/html',
href: `${nconf.get('url')}/user/${slug}`,
},
];
} else {
response.aliases = [nconf.get('url')];
response.links = [
{
rel: 'self',
type: 'application/activity+json',
href: `${nconf.get('url')}/actor`, // actor
},
];
res.status(200).json(response);
} catch (e) {
res.sendStatus(400);
}
res.status(200).json(response);
};
function application(response) {
response.aliases = [nconf.get('url')];
response.links = [
{
rel: 'self',
type: 'application/activity+json',
href: `${nconf.get('url')}/actor`, // actor
},
];
return response;
}
async function profile(callerUid, uid, response) {
const canView = await privileges.global.can('view:users', callerUid);
if (!canView) {
throw new Error('[[error:no-privileges]]');
}
const slug = await user.getUserField(uid, 'userslug');
response.aliases = [
`${nconf.get('url')}/uid/${uid}`,
`${nconf.get('url')}/user/${slug}`,
];
response.links = [
{
rel: 'self',
type: 'application/activity+json',
href: `${nconf.get('url')}/uid/${uid}`, // actor
},
{
rel: 'http://webfinger.net/rel/profile-page',
type: 'text/html',
href: `${nconf.get('url')}/user/${slug}`,
},
];
return response;
}
async function category(callerUid, cid, response) {
const canFind = await privileges.categories.can('find', cid, callerUid);
if (!canFind) {
throw new Error('[[error:no-privileges]]');
}
const slug = await categories.getCategoryField(cid, 'slug');
response.aliases = [`${nconf.get('url')}/category/${slug}`];
response.links = [
{
rel: 'self',
type: 'application/activity+json',
href: `${nconf.get('url')}/category/${cid}`, // actor
},
];
return response;
}

View File

@@ -15,17 +15,21 @@ module.exports = function (app, middleware, controllers) {
const middlewares = [middleware.activitypub.enabled, middleware.activitypub.assertS2S];
app.get('/actor', middlewares, controllers.activitypub.actors.application);
app.get('/uid/:uid', middlewares, controllers.activitypub.actors.user);
app.get('/user/:userslug', [...middlewares, middleware.exposeUid], controllers.activitypub.actors.userBySlug);
app.get('/uid/:uid/inbox', middlewares, controllers.activitypub.getInbox);
app.post('/uid/:uid/inbox', [...middlewares, middleware.activitypub.validate], controllers.activitypub.postInbox);
app.get('/uid/:uid/outbox', middlewares, controllers.activitypub.getOutbox);
app.post('/uid/:uid/outbox', middlewares, controllers.activitypub.postOutbox);
app.get('/uid/:uid/following', middlewares, controllers.activitypub.getFollowing);
app.get('/uid/:uid/followers', middlewares, controllers.activitypub.getFollowers);
app.get('/post/:pid', middlewares, controllers.activitypub.actors.note);
app.get('/category/:cid', middlewares, controllers.activitypub.actors.category);
app.get('/category/:cid/inbox', middlewares, controllers.activitypub.getInbox);
app.post('/category/:cid/inbox', [...middlewares, middleware.activitypub.validate], controllers.activitypub.postInbox);
app.get('/category/:cid/outbox', middlewares, controllers.activitypub.getCategoryOutbox);
app.post('/category/:cid/outbox', middlewares, controllers.activitypub.postOutbox);
};