Publish pinned notes (#2731)

This commit is contained in:
MeiMei 2018-09-18 13:08:27 +09:00 committed by syuilo
parent bec48319ec
commit 11496d887e
9 changed files with 135 additions and 2 deletions

View File

@ -0,0 +1,9 @@
import config from '../../../config';
import { ILocalUser } from '../../../models/user';
export default (user: ILocalUser, target: any, object: any) => ({
type: 'Add',
actor: `${config.url}/users/${user._id}`,
target,
object
});

View File

@ -4,8 +4,9 @@
* @param totalItems Total number of items * @param totalItems Total number of items
* @param first URL of first page (optional) * @param first URL of first page (optional)
* @param last URL of last page (optional) * @param last URL of last page (optional)
* @param orderedItems attached objects (optional)
*/ */
export default function(id: string, totalItems: any, first: string, last: string) { export default function(id: string, totalItems: any, first?: string, last?: string, orderedItems?: object) {
const page: any = { const page: any = {
id, id,
type: 'OrderedCollection', type: 'OrderedCollection',
@ -14,6 +15,7 @@ export default function(id: string, totalItems: any, first: string, last: string
if (first) page.first = first; if (first) page.first = first;
if (last) page.last = last; if (last) page.last = last;
if (orderedItems) page.orderedItems = orderedItems;
return page; return page;
} }

View File

@ -21,6 +21,7 @@ export default async (user: ILocalUser) => {
outbox: `${id}/outbox`, outbox: `${id}/outbox`,
followers: `${id}/followers`, followers: `${id}/followers`,
following: `${id}/following`, following: `${id}/following`,
featured: `${id}/collections/featured`,
sharedInbox: `${config.url}/inbox`, sharedInbox: `${config.url}/inbox`,
url: `${config.url}/@${user.username}`, url: `${config.url}/@${user.username}`,
preferredUsername: user.username, preferredUsername: user.username,

View File

@ -0,0 +1,9 @@
import config from '../../../config';
import { ILocalUser } from '../../../models/user';
export default (user: ILocalUser, target: any, object: any) => ({
type: 'Remove',
actor: `${config.url}/users/${user._id}`,
target,
object
});

View File

@ -53,6 +53,7 @@ export interface IPerson extends IObject {
publicKey: any; publicKey: any;
followers: any; followers: any;
following: any; following: any;
featured?: any;
outbox: any; outbox: any;
endpoints: string[]; endpoints: string[];
} }

View File

@ -13,6 +13,7 @@ import renderPerson from '../remote/activitypub/renderer/person';
import Outbox, { packActivity } from './activitypub/outbox'; import Outbox, { packActivity } from './activitypub/outbox';
import Followers from './activitypub/followers'; import Followers from './activitypub/followers';
import Following from './activitypub/following'; import Following from './activitypub/following';
import Featured from './activitypub/featured';
// Init router // Init router
const router = new Router(); const router = new Router();
@ -102,6 +103,9 @@ router.get('/users/:user/followers', Followers);
// following // following
router.get('/users/:user/following', Following); router.get('/users/:user/following', Following);
// featured
router.get('/users/:user/collections/featured', Featured);
// publickey // publickey
router.get('/users/:user/publickey', async ctx => { router.get('/users/:user/publickey', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user); const userId = new mongo.ObjectID(ctx.params.user);

View File

@ -0,0 +1,38 @@
import * as mongo from 'mongodb';
import * as Router from 'koa-router';
import config from '../../config';
import User from '../../models/user';
import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import { setResponseType } from '../activitypub';
import Note from '../../models/note';
import renderNote from '../../remote/activitypub/renderer/note';
export default async (ctx: Router.IRouterContext) => {
const userId = new mongo.ObjectID(ctx.params.user);
// Verify user
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const pinnedNoteIds = user.pinnedNoteIds || [];
const pinnedNotes = await Promise.all(pinnedNoteIds.map(id => Note.findOne({ _id: id })));
const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note)));
const rendered = renderOrderedCollection(
`${config.url}/users/${userId}/collections/featured`,
renderedNotes.length, null, null, renderedNotes
);
ctx.body = pack(rendered);
setResponseType(ctx);
};

View File

@ -2,6 +2,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
import User, { ILocalUser } from '../../../../models/user'; import User, { ILocalUser } from '../../../../models/user';
import Note from '../../../../models/note'; import Note from '../../../../models/note';
import { pack } from '../../../../models/user'; import { pack } from '../../../../models/user';
import { deliverPinnedChange } from '../../../../services/i/pin';
/** /**
* Pin note * Pin note
@ -21,6 +22,9 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
return rej('note not found'); return rej('note not found');
} }
let addedId;
let removedId;
const pinnedNoteIds = user.pinnedNoteIds || []; const pinnedNoteIds = user.pinnedNoteIds || [];
if (pinnedNoteIds.some(id => id.equals(note._id))) { if (pinnedNoteIds.some(id => id.equals(note._id))) {
@ -28,9 +32,10 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
} }
pinnedNoteIds.unshift(note._id); pinnedNoteIds.unshift(note._id);
addedId = note._id;
if (pinnedNoteIds.length > 5) { if (pinnedNoteIds.length > 5) {
pinnedNoteIds.pop(); removedId = pinnedNoteIds.pop();
} }
await User.update(user._id, { await User.update(user._id, {
@ -44,6 +49,9 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
detail: true detail: true
}); });
// Send Add/Remove to followers
deliverPinnedChange(user._id, removedId, addedId);
// Send response // Send response
res(iObj); res(iObj);
}); });

61
src/services/i/pin.ts Normal file
View File

@ -0,0 +1,61 @@
import config from '../../config';
import * as mongo from 'mongodb';
import User, { isLocalUser, isRemoteUser, ILocalUser } from '../../models/user';
import Following from '../../models/following';
import renderAdd from '../../remote/activitypub/renderer/add';
import renderRemove from '../../remote/activitypub/renderer/remove';
import packAp from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
export async function deliverPinnedChange(userId: mongo.ObjectID, oldId: mongo.ObjectID, newId: mongo.ObjectID) {
const user = await User.findOne({
_id: userId
});
if (!isLocalUser(user)) return;
const queue = await CreateRemoteInboxes(user);
if (queue.length < 1) return;
const target = `${config.url}/users/${user._id}/collections/featured`;
if (oldId) {
const oldItem = `${config.url}/notes/${oldId}`;
const content = packAp(renderRemove(user, target, oldItem));
queue.forEach(inbox => {
deliver(user, content, inbox);
});
}
if (newId) {
const newItem = `${config.url}/notes/${newId}`;
const content = packAp(renderAdd(user, target, newItem));
queue.forEach(inbox => {
deliver(user, content, inbox);
});
}
}
/**
* inboxリストを作成する
* @param user
*/
async function CreateRemoteInboxes(user: ILocalUser): Promise<string[]> {
const followers = await Following.find({
followeeId: user._id
});
const queue: string[] = [];
followers.map(following => {
const follower = following._follower;
if (isRemoteUser(follower)) {
const inbox = follower.sharedInbox || follower.inbox;
if (!queue.includes(inbox)) queue.push(inbox);
}
});
return queue;
}