From c5f23bce7864bc1cffb82b54f62e019c4c04137d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Apr 2018 17:05:14 +0900 Subject: [PATCH] Implement like --- src/remote/activitypub/act/index.ts | 11 ++- src/remote/activitypub/act/like.ts | 55 ++--------- src/remote/activitypub/renderer/like.ts | 9 ++ src/remote/activitypub/renderer/note.ts | 4 +- src/remote/activitypub/type.ts | 7 +- .../api/endpoints/posts/reactions/create.ts | 88 ++--------------- src/services/post/reaction/create.ts | 94 +++++++++++++++++++ 7 files changed, 131 insertions(+), 137 deletions(-) create mode 100644 src/remote/activitypub/renderer/like.ts diff --git a/src/remote/activitypub/act/index.ts b/src/remote/activitypub/act/index.ts index 5fcdb61748..45d7bd16a9 100644 --- a/src/remote/activitypub/act/index.ts +++ b/src/remote/activitypub/act/index.ts @@ -1,9 +1,10 @@ +import { Object } from '../type'; +import { IRemoteUser } from '../../../models/user'; import create from './create'; import performDeleteActivity from './delete'; import follow from './follow'; import undo from './undo'; -import { Object } from '../type'; -import { IRemoteUser } from '../../../models/user'; +import like from './like'; const self = async (actor: IRemoteUser, activity: Object): Promise => { switch (activity.type) { @@ -23,6 +24,10 @@ const self = async (actor: IRemoteUser, activity: Object): Promise => { // noop break; + case 'Like': + await like(actor, activity); + break; + case 'Undo': await undo(actor, activity); break; @@ -33,7 +38,7 @@ const self = async (actor: IRemoteUser, activity: Object): Promise => { break; default: - console.warn(`unknown activity type: ${activity.type}`); + console.warn(`unknown activity type: ${(activity as any).type}`); return null; } }; diff --git a/src/remote/activitypub/act/like.ts b/src/remote/activitypub/act/like.ts index ea53242017..2f5e3f807d 100644 --- a/src/remote/activitypub/act/like.ts +++ b/src/remote/activitypub/act/like.ts @@ -1,10 +1,10 @@ -import { MongoError } from 'mongodb'; -import Reaction, { IPostReaction } from '../../../models/post-reaction'; import Post from '../../../models/post'; -import queue from '../../../queue'; +import { IRemoteUser } from '../../../models/user'; +import { ILike } from '../type'; +import create from '../../../services/post/reaction/create'; -export default async (resolver, actor, activity, distribute) => { - const id = activity.object.id || activity.object; +export default async (actor: IRemoteUser, activity: ILike) => { + const id = typeof activity.object == 'string' ? activity.object : activity.object.id; // Transform: // https://misskey.ex/@syuilo/xxxx to @@ -16,48 +16,5 @@ export default async (resolver, actor, activity, distribute) => { throw new Error(); } - if (!distribute) { - const { _id } = await Reaction.findOne({ - userId: actor._id, - postId: post._id - }); - - return { - resolver, - object: { $ref: 'postPeactions', $id: _id } - }; - } - - const promisedReaction = Reaction.insert({ - createdAt: new Date(), - userId: actor._id, - postId: post._id, - reaction: 'pudding' - }).then(reaction => new Promise((resolve, reject) => { - queue.create('http', { - type: 'reaction', - reactionId: reaction._id - }).save(error => { - if (error) { - reject(error); - } else { - resolve(reaction); - } - }); - }), async error => { - // duplicate key error - if (error instanceof MongoError && error.code === 11000) { - return Reaction.findOne({ - userId: actor._id, - postId: post._id - }); - } - - throw error; - }); - - return promisedReaction.then(({ _id }) => ({ - resolver, - object: { $ref: 'postPeactions', $id: _id } - })); + await create(actor, post, 'pudding'); }; diff --git a/src/remote/activitypub/renderer/like.ts b/src/remote/activitypub/renderer/like.ts new file mode 100644 index 0000000000..903b10789e --- /dev/null +++ b/src/remote/activitypub/renderer/like.ts @@ -0,0 +1,9 @@ +import config from '../../../config'; + +export default (user, post) => { + return { + type: 'Like', + actor: `${config.url}/@${user.username}`, + object: post.uri ? post.uri : `${config.url}/posts/${post._id}` + }; +}; diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index b971a53951..bbab63db36 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -23,7 +23,7 @@ export default async (user: IUser, post: IPost) => { }); if (inReplyToUser !== null) { - inReplyTo = inReplyToPost.uri || `${config.url}/@${inReplyToUser.username}/${inReplyToPost._id}`; + inReplyTo = inReplyToPost.uri || `${config.url}/posts/${inReplyToPost._id}`; } } } else { @@ -33,7 +33,7 @@ export default async (user: IUser, post: IPost) => { const attributedTo = `${config.url}/@${user.username}`; return { - id: `${attributedTo}/${post._id}`, + id: `${config.url}/posts/${post._id}`, type: 'Note', attributedTo, content: post.textHtml, diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index c07b806be8..450d5906d8 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -55,6 +55,10 @@ export interface IAccept extends IActivity { type: 'Accept'; } +export interface ILike extends IActivity { + type: 'Like'; +} + export type Object = ICollection | IOrderedCollection | @@ -62,4 +66,5 @@ export type Object = IDelete | IUndo | IFollow | - IAccept; + IAccept | + ILike; diff --git a/src/server/api/endpoints/posts/reactions/create.ts b/src/server/api/endpoints/posts/reactions/create.ts index f1b0c7dd29..71fa6a2955 100644 --- a/src/server/api/endpoints/posts/reactions/create.ts +++ b/src/server/api/endpoints/posts/reactions/create.ts @@ -3,20 +3,11 @@ */ import $ from 'cafy'; import Reaction from '../../../../../models/post-reaction'; -import Post, { pack as packPost } from '../../../../../models/post'; -import { pack as packUser } from '../../../../../models/user'; -import Watching from '../../../../../models/post-watching'; -import watch from '../../../../../post/watch'; -import { publishPostStream } from '../../../../../publishers/stream'; -import notify from '../../../../../publishers/notify'; -import pushSw from '../../../../../publishers/push-sw'; +import Post from '../../../../../models/post'; +import create from '../../../../../services/post/reaction/create'; /** * React to a post - * - * @param {any} params - * @param {any} user - * @return {Promise} */ module.exports = (params, user) => new Promise(async (res, rej) => { // Get 'postId' parameter @@ -46,78 +37,11 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('post not found'); } - // Myself - if (post.userId.equals(user._id)) { - return rej('cannot react to my post'); + try { + await create(user, post, reaction); + } catch (e) { + rej(e); } - // if already reacted - const exist = await Reaction.findOne({ - postId: post._id, - userId: user._id, - deletedAt: { $exists: false } - }); - - if (exist !== null) { - return rej('already reacted'); - } - - // Create reaction - await Reaction.insert({ - createdAt: new Date(), - postId: post._id, - userId: user._id, - reaction: reaction - }); - - // Send response res(); - - const inc = {}; - inc[`reactionCounts.${reaction}`] = 1; - - // Increment reactions count - await Post.update({ _id: post._id }, { - $inc: inc - }); - - publishPostStream(post._id, 'reacted'); - - // Notify - notify(post.userId, user._id, 'reaction', { - postId: post._id, - reaction: reaction - }); - - pushSw(post.userId, 'reaction', { - user: await packUser(user, post.userId), - post: await packPost(post, post.userId), - reaction: reaction - }); - - // Fetch watchers - Watching - .find({ - postId: post._id, - userId: { $ne: user._id }, - // 削除されたドキュメントは除く - deletedAt: { $exists: false } - }, { - fields: { - userId: true - } - }) - .then(watchers => { - watchers.forEach(watcher => { - notify(watcher.userId, user._id, 'reaction', { - postId: post._id, - reaction: reaction - }); - }); - }); - - // この投稿をWatchする - if (user.account.settings.autoWatch !== false) { - watch(user._id, post); - } }); diff --git a/src/services/post/reaction/create.ts b/src/services/post/reaction/create.ts index e69de29bb2..c26efcfc75 100644 --- a/src/services/post/reaction/create.ts +++ b/src/services/post/reaction/create.ts @@ -0,0 +1,94 @@ +import { IUser, pack as packUser, isLocalUser, isRemoteUser } from '../../../models/user'; +import Post, { IPost, pack as packPost } from '../../../models/post'; +import PostReaction from '../../../models/post-reaction'; +import { publishPostStream } from '../../../publishers/stream'; +import notify from '../../../publishers/notify'; +import pushSw from '../../../publishers/push-sw'; +import PostWatching from '../../../models/post-watching'; +import watch from '../watch'; +import renderLike from '../../../remote/activitypub/renderer/like'; +import { deliver } from '../../../queue'; +import context from '../../../remote/activitypub/renderer/context'; + +export default async (user: IUser, post: IPost, reaction: string) => new Promise(async (res, rej) => { + // Myself + if (post.userId.equals(user._id)) { + return rej('cannot react to my post'); + } + + // if already reacted + const exist = await PostReaction.findOne({ + postId: post._id, + userId: user._id + }); + + if (exist !== null) { + return rej('already reacted'); + } + + // Create reaction + await PostReaction.insert({ + createdAt: new Date(), + postId: post._id, + userId: user._id, + reaction + }); + + res(); + + const inc = {}; + inc[`reactionCounts.${reaction}`] = 1; + + // Increment reactions count + await Post.update({ _id: post._id }, { + $inc: inc + }); + + publishPostStream(post._id, 'reacted'); + + // Notify + notify(post.userId, user._id, 'reaction', { + postId: post._id, + reaction: reaction + }); + + pushSw(post.userId, 'reaction', { + user: await packUser(user, post.userId), + post: await packPost(post, post.userId), + reaction: reaction + }); + + // Fetch watchers + PostWatching + .find({ + postId: post._id, + userId: { $ne: user._id } + }, { + fields: { + userId: true + } + }) + .then(watchers => { + watchers.forEach(watcher => { + notify(watcher.userId, user._id, 'reaction', { + postId: post._id, + reaction: reaction + }); + }); + }); + + // ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする + if (isLocalUser(user) && user.account.settings.autoWatch !== false) { + watch(user._id, post); + } + + //#region 配信 + const content = renderLike(user, post); + content['@context'] = context; + + // リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送 + if (isLocalUser(user) && isRemoteUser(post._user)) { + deliver(user, content, post._user.account.inbox).save(); + } + //#endregion +});