From f0a29721c9fb10f97faf386bc9d6b1b2fad97895 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Apr 2019 21:50:36 +0900 Subject: Use PostgreSQL instead of MongoDB (#4572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * Update note.ts * Update timeline.ts * Update core.ts * wip * Update generate-visibility-query.ts * wip * wip * wip * wip * wip * Update global-timeline.ts * wip * wip * wip * Update vote.ts * wip * wip * Update create.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update files.ts * wip * wip * Update CONTRIBUTING.md * wip * wip * wip * wip * wip * wip * wip * wip * Update read-notification.ts * wip * wip * wip * wip * wip * wip * wip * Update cancel.ts * wip * wip * wip * Update show.ts * wip * wip * Update gen-id.ts * Update create.ts * Update id.ts * wip * wip * wip * wip * wip * wip * wip * Docker: Update files about Docker (#4599) * Docker: Use cache if files used by `yarn install` was not updated This patch reduces the number of times to installing node_modules. For example, `yarn install` step will be skipped when only ".config/default.yml" is updated. * Docker: Migrate MongoDB to Postgresql Misskey uses Postgresql as a database instead of Mongodb since version 11. * Docker: Uncomment about data persistence This patch will save a lot of databases. * wip * wip * wip * Update activitypub.ts * wip * wip * wip * Update logs.ts * wip * Update drive-file.ts * Update register.ts * wip * wip * Update mentions.ts * wip * wip * wip * Update recommendation.ts * wip * Update index.ts * wip * Update recommendation.ts * Doc: Update docker.ja.md and docker.en.md (#1) (#4608) Update how to set up misskey. * wip * :v: * wip * Update note.ts * Update postgre.ts * wip * wip * wip * wip * Update add-file.ts * wip * wip * wip * Clean up * Update logs.ts * wip * :pizza: * wip * Ad notes * wip * Update api-visibility.ts * Update note.ts * Update add-file.ts * tests * tests * Update postgre.ts * Update utils.ts * wip * wip * Refactor * wip * Refactor * wip * wip * Update show-users.ts * Update update-instance.ts * wip * Update feed.ts * Update outbox.ts * Update outbox.ts * Update user.ts * wip * Update list.ts * Update update-hashtag.ts * wip * Update update-hashtag.ts * Refactor * Update update.ts * wip * wip * :v: * clean up * docs * Update push.ts * wip * Update api.ts * wip * :v: * Update make-pagination-query.ts * :v: * Delete hashtags.ts * Update instances.ts * Update instances.ts * Update create.ts * Update search.ts * Update reversi-game.ts * Update signup.ts * Update user.ts * id * Update example.yml * :art: * objectid * fix * reversi * reversi * Fix bug of chart engine * Add test of chart engine * Improve test * Better testing * Improve chart engine * Refactor * Add test of chart engine * Refactor * Add chart test * Fix bug * コミットし忘れ * Refactoring * :v: * Add tests * Add test * Extarct note tests * Refactor * 存在しないユーザーにメンションできなくなっていた問題を修正 * Fix bug * Update update-meta.ts * Fix bug * Update mention.vue * Fix bug * Update meta.ts * Update CONTRIBUTING.md * Fix bug * Fix bug * Fix bug * Clean up * Clean up * Update notification.ts * Clean up * Add mute tests * Add test * Refactor * Add test * Fix test * Refactor * Refactor * Add tests * Update utils.ts * Update utils.ts * Fix test * Update package.json * Update update.ts * Update manifest.ts * Fix bug * Fix bug * Add test * :art: * Update endpoint permissions * Updaye permisison * Update person.ts #4299 * データベースと同期しないように * Fix bug * Fix bug * Update reversi-game.ts * Use a feature of Node v11.7.0 to extract a public key (#4644) * wip * wip * :v: * Refactoring #1540 * test * test * test * test * test * test * test * Fix bug * Fix test * :sushi: * wip * #4471 * Add test for #4335 * Refactor * Fix test * Add tests * :clock4: * Fix bug * Add test * Add test * rename * Fix bug --- src/remote/activitypub/kernel/accept/follow.ts | 10 +- src/remote/activitypub/kernel/accept/index.ts | 2 +- src/remote/activitypub/kernel/add/index.ts | 4 +- src/remote/activitypub/kernel/announce/index.ts | 2 +- src/remote/activitypub/kernel/announce/note.ts | 10 +- src/remote/activitypub/kernel/block/index.ts | 10 +- src/remote/activitypub/kernel/create/image.ts | 2 +- src/remote/activitypub/kernel/create/index.ts | 2 +- src/remote/activitypub/kernel/create/note.ts | 2 +- src/remote/activitypub/kernel/delete/index.ts | 6 +- src/remote/activitypub/kernel/delete/note.ts | 8 +- src/remote/activitypub/kernel/follow.ts | 10 +- src/remote/activitypub/kernel/index.ts | 2 +- src/remote/activitypub/kernel/like.ts | 11 +- src/remote/activitypub/kernel/reject/follow.ts | 10 +- src/remote/activitypub/kernel/reject/index.ts | 2 +- src/remote/activitypub/kernel/remove/index.ts | 4 +- src/remote/activitypub/kernel/undo/block.ts | 10 +- src/remote/activitypub/kernel/undo/follow.ts | 24 +-- src/remote/activitypub/kernel/undo/index.ts | 2 +- src/remote/activitypub/kernel/undo/like.ts | 11 +- src/remote/activitypub/kernel/update/index.ts | 2 +- src/remote/activitypub/misc/get-note-html.ts | 6 +- src/remote/activitypub/models/image.ts | 25 ++- src/remote/activitypub/models/note.ts | 88 +++++---- src/remote/activitypub/models/person.ts | 246 ++++++++++-------------- src/remote/activitypub/models/question.ts | 41 ++-- src/remote/activitypub/perform.ts | 2 +- src/remote/activitypub/renderer/accept.ts | 4 +- src/remote/activitypub/renderer/add.ts | 4 +- src/remote/activitypub/renderer/announce.ts | 6 +- src/remote/activitypub/renderer/block.ts | 4 +- src/remote/activitypub/renderer/create.ts | 6 +- src/remote/activitypub/renderer/delete.ts | 4 +- src/remote/activitypub/renderer/document.ts | 10 +- src/remote/activitypub/renderer/emoji.ts | 4 +- src/remote/activitypub/renderer/follow-user.ts | 14 +- src/remote/activitypub/renderer/follow.ts | 9 +- src/remote/activitypub/renderer/image.ts | 10 +- src/remote/activitypub/renderer/key.ts | 13 +- src/remote/activitypub/renderer/like.ts | 10 +- src/remote/activitypub/renderer/mention.ts | 9 +- src/remote/activitypub/renderer/note.ts | 91 ++++----- src/remote/activitypub/renderer/person.ts | 41 ++-- src/remote/activitypub/renderer/question.ts | 19 +- src/remote/activitypub/renderer/reject.ts | 4 +- src/remote/activitypub/renderer/remove.ts | 4 +- src/remote/activitypub/renderer/undo.ts | 6 +- src/remote/activitypub/renderer/update.ts | 6 +- src/remote/activitypub/renderer/vote.ts | 21 +- src/remote/activitypub/request.ts | 17 +- src/remote/activitypub/resolver.ts | 2 +- src/remote/resolve-user.ts | 21 +- 53 files changed, 427 insertions(+), 466 deletions(-) (limited to 'src/remote') diff --git a/src/remote/activitypub/kernel/accept/follow.ts b/src/remote/activitypub/kernel/accept/follow.ts index 07c820c28a..816fcbadbf 100644 --- a/src/remote/activitypub/kernel/accept/follow.ts +++ b/src/remote/activitypub/kernel/accept/follow.ts @@ -1,8 +1,8 @@ -import * as mongo from 'mongodb'; -import User, { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import config from '../../../../config'; import accept from '../../../../services/following/requests/accept'; import { IFollow } from '../../type'; +import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; @@ -11,11 +11,11 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise => { return null; } - const follower = await User.findOne({ - _id: new mongo.ObjectID(id.split('/').pop()) + const follower = await Users.findOne({ + id: id.split('/').pop() }); - if (follower === null) { + if (follower == null) { throw new Error('follower not found'); } diff --git a/src/remote/activitypub/kernel/accept/index.ts b/src/remote/activitypub/kernel/accept/index.ts index 443c1935d6..5a27ce1d4d 100644 --- a/src/remote/activitypub/kernel/accept/index.ts +++ b/src/remote/activitypub/kernel/accept/index.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import acceptFollow from './follow'; import { IAccept, IFollow } from '../../type'; import { apLogger } from '../../logger'; diff --git a/src/remote/activitypub/kernel/add/index.ts b/src/remote/activitypub/kernel/add/index.ts index eb2dba5b21..d16f0a4a0d 100644 --- a/src/remote/activitypub/kernel/add/index.ts +++ b/src/remote/activitypub/kernel/add/index.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { IAdd } from '../../type'; import { resolveNote } from '../../models/note'; import { addPinned } from '../../../../services/i/pin'; @@ -14,7 +14,7 @@ export default async (actor: IRemoteUser, activity: IAdd): Promise => { if (activity.target === actor.featured) { const note = await resolveNote(activity.object); - await addPinned(actor, note._id); + await addPinned(actor, note.id); return; } diff --git a/src/remote/activitypub/kernel/announce/index.ts b/src/remote/activitypub/kernel/announce/index.ts index 5f738da6c7..ebd5a27b92 100644 --- a/src/remote/activitypub/kernel/announce/index.ts +++ b/src/remote/activitypub/kernel/announce/index.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import announceNote from './note'; import { IAnnounce, INote } from '../../type'; import { apLogger } from '../../logger'; diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index 912936bef8..403fc66bed 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver'; import post from '../../../../services/note/create'; -import { IRemoteUser, IUser } from '../../../../models/user'; +import { IRemoteUser, User } from '../../../../models/entities/user'; import { IAnnounce, INote } from '../../type'; import { fetchNote, resolveNote } from '../../models/note'; import { resolvePerson } from '../../models/person'; import { apLogger } from '../../logger'; import { extractDbHost } from '../../../../misc/convert-host'; -import Instance from '../../../../models/instance'; +import fetchMeta from '../../../../misc/fetch-meta'; const logger = apLogger; @@ -27,8 +27,8 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: // アナウンス先をブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく - const instance = await Instance.findOne({ host: extractDbHost(uri) }); - if (instance && instance.isBlocked) return; + const meta = await fetchMeta(); + if (meta.blockedHosts.includes(extractDbHost(uri))) return; // 既に同じURIを持つものが登録されていないかチェック const exist = await fetchNote(uri); @@ -55,7 +55,7 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: //#region Visibility const visibility = getVisibility(activity.to, activity.cc, actor); - let visibleUsers: IUser[] = []; + let visibleUsers: User[] = []; if (visibility == 'specified') { visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); } diff --git a/src/remote/activitypub/kernel/block/index.ts b/src/remote/activitypub/kernel/block/index.ts index a10163016c..48e251dd9b 100644 --- a/src/remote/activitypub/kernel/block/index.ts +++ b/src/remote/activitypub/kernel/block/index.ts @@ -1,9 +1,9 @@ -import * as mongo from 'mongodb'; -import User, { IRemoteUser } from '../../../../models/user'; import config from '../../../../config'; import { IBlock } from '../../type'; import block from '../../../../services/blocking/create'; import { apLogger } from '../../logger'; +import { Users } from '../../../../models'; +import { IRemoteUser } from '../../../../models/entities/user'; const logger = apLogger; @@ -18,11 +18,9 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise => { return null; } - const blockee = await User.findOne({ - _id: new mongo.ObjectID(id.split('/').pop()) - }); + const blockee = await Users.findOne(id.split('/').pop()); - if (blockee === null) { + if (blockee == null) { throw new Error('blockee not found'); } diff --git a/src/remote/activitypub/kernel/create/image.ts b/src/remote/activitypub/kernel/create/image.ts index 9c19abbcc4..7720e8f1bd 100644 --- a/src/remote/activitypub/kernel/create/image.ts +++ b/src/remote/activitypub/kernel/create/image.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { createImage } from '../../models/image'; export default async function(actor: IRemoteUser, image: any): Promise { diff --git a/src/remote/activitypub/kernel/create/index.ts b/src/remote/activitypub/kernel/create/index.ts index 6e314d0b82..0326b591f8 100644 --- a/src/remote/activitypub/kernel/create/index.ts +++ b/src/remote/activitypub/kernel/create/index.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import createImage from './image'; import createNote from './note'; import { ICreate } from '../../type'; diff --git a/src/remote/activitypub/kernel/create/note.ts b/src/remote/activitypub/kernel/create/note.ts index 0f874b9fbf..70e61bdf1b 100644 --- a/src/remote/activitypub/kernel/create/note.ts +++ b/src/remote/activitypub/kernel/create/note.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { createNote, fetchNote } from '../../models/note'; /** diff --git a/src/remote/activitypub/kernel/delete/index.ts b/src/remote/activitypub/kernel/delete/index.ts index c9c385b1fa..fab5e7ab64 100644 --- a/src/remote/activitypub/kernel/delete/index.ts +++ b/src/remote/activitypub/kernel/delete/index.ts @@ -1,9 +1,9 @@ import Resolver from '../../resolver'; import deleteNote from './note'; -import Note from '../../../../models/note'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { IDelete } from '../../type'; import { apLogger } from '../../logger'; +import { Notes } from '../../../../models'; /** * 削除アクティビティを捌きます @@ -27,7 +27,7 @@ export default async (actor: IRemoteUser, activity: IDelete): Promise => { break; case 'Tombstone': - const note = await Note.findOne({ uri }); + const note = await Notes.findOne({ uri }); if (note != null) { deleteNote(actor, uri); } diff --git a/src/remote/activitypub/kernel/delete/note.ts b/src/remote/activitypub/kernel/delete/note.ts index f67919c56b..b146e68a07 100644 --- a/src/remote/activitypub/kernel/delete/note.ts +++ b/src/remote/activitypub/kernel/delete/note.ts @@ -1,20 +1,20 @@ -import Note from '../../../../models/note'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import deleteNode from '../../../../services/note/delete'; import { apLogger } from '../../logger'; +import { Notes } from '../../../../models'; const logger = apLogger; export default async function(actor: IRemoteUser, uri: string): Promise { logger.info(`Deleting the Note: ${uri}`); - const note = await Note.findOne({ uri }); + const note = await Notes.findOne({ uri }); if (note == null) { throw new Error('note not found'); } - if (!note.userId.equals(actor._id)) { + if (note.userId !== actor.id) { throw new Error('投稿を削除しようとしているユーザーは投稿の作成者ではありません'); } diff --git a/src/remote/activitypub/kernel/follow.ts b/src/remote/activitypub/kernel/follow.ts index e2db70b20d..e6c8833f3a 100644 --- a/src/remote/activitypub/kernel/follow.ts +++ b/src/remote/activitypub/kernel/follow.ts @@ -1,8 +1,8 @@ -import * as mongo from 'mongodb'; -import User, { IRemoteUser } from '../../../models/user'; +import { IRemoteUser } from '../../../models/entities/user'; import config from '../../../config'; import follow from '../../../services/following/create'; import { IFollow } from '../type'; +import { Users } from '../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; @@ -11,11 +11,9 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise => { return null; } - const followee = await User.findOne({ - _id: new mongo.ObjectID(id.split('/').pop()) - }); + const followee = await Users.findOne(id.split('/').pop()); - if (followee === null) { + if (followee == null) { throw new Error('followee not found'); } diff --git a/src/remote/activitypub/kernel/index.ts b/src/remote/activitypub/kernel/index.ts index 4f7a5c91fd..4a57d0675e 100644 --- a/src/remote/activitypub/kernel/index.ts +++ b/src/remote/activitypub/kernel/index.ts @@ -1,5 +1,5 @@ import { Object } from '../type'; -import { IRemoteUser } from '../../../models/user'; +import { IRemoteUser } from '../../../models/entities/user'; import create from './create'; import performDeleteActivity from './delete'; import performUpdateActivity from './update'; diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts index ed35da8133..86dd8fb33d 100644 --- a/src/remote/activitypub/kernel/like.ts +++ b/src/remote/activitypub/kernel/like.ts @@ -1,8 +1,7 @@ -import * as mongo from 'mongodb'; -import Note from '../../../models/note'; -import { IRemoteUser } from '../../../models/user'; +import { IRemoteUser } from '../../../models/entities/user'; import { ILike } from '../type'; import create from '../../../services/note/reaction/create'; +import { Notes } from '../../../models'; export default async (actor: IRemoteUser, activity: ILike) => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; @@ -10,10 +9,10 @@ export default async (actor: IRemoteUser, activity: ILike) => { // Transform: // https://misskey.ex/notes/xxxx to // xxxx - const noteId = new mongo.ObjectID(id.split('/').pop()); + const noteId = id.split('/').pop(); - const note = await Note.findOne({ _id: noteId }); - if (note === null) { + const note = await Notes.findOne(noteId); + if (note == null) { throw new Error(); } diff --git a/src/remote/activitypub/kernel/reject/follow.ts b/src/remote/activitypub/kernel/reject/follow.ts index 35cd2ec0c9..b06ae6fb96 100644 --- a/src/remote/activitypub/kernel/reject/follow.ts +++ b/src/remote/activitypub/kernel/reject/follow.ts @@ -1,8 +1,8 @@ -import * as mongo from 'mongodb'; -import User, { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import config from '../../../../config'; import reject from '../../../../services/following/requests/reject'; import { IFollow } from '../../type'; +import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; @@ -11,11 +11,9 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise => { return null; } - const follower = await User.findOne({ - _id: new mongo.ObjectID(id.split('/').pop()) - }); + const follower = await Users.findOne(id.split('/').pop()); - if (follower === null) { + if (follower == null) { throw new Error('follower not found'); } diff --git a/src/remote/activitypub/kernel/reject/index.ts b/src/remote/activitypub/kernel/reject/index.ts index c3585abbb6..8ece5cf174 100644 --- a/src/remote/activitypub/kernel/reject/index.ts +++ b/src/remote/activitypub/kernel/reject/index.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import rejectFollow from './follow'; import { IReject, IFollow } from '../../type'; import { apLogger } from '../../logger'; diff --git a/src/remote/activitypub/kernel/remove/index.ts b/src/remote/activitypub/kernel/remove/index.ts index 91b207c80d..ae33be59dc 100644 --- a/src/remote/activitypub/kernel/remove/index.ts +++ b/src/remote/activitypub/kernel/remove/index.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { IRemove } from '../../type'; import { resolveNote } from '../../models/note'; import { removePinned } from '../../../../services/i/pin'; @@ -14,7 +14,7 @@ export default async (actor: IRemoteUser, activity: IRemove): Promise => { if (activity.target === actor.featured) { const note = await resolveNote(activity.object); - await removePinned(actor, note._id); + await removePinned(actor, note.id); return; } diff --git a/src/remote/activitypub/kernel/undo/block.ts b/src/remote/activitypub/kernel/undo/block.ts index 4a22ac7924..c916a00737 100644 --- a/src/remote/activitypub/kernel/undo/block.ts +++ b/src/remote/activitypub/kernel/undo/block.ts @@ -1,9 +1,9 @@ -import * as mongo from 'mongodb'; -import User, { IRemoteUser } from '../../../../models/user'; import config from '../../../../config'; import { IBlock } from '../../type'; import unblock from '../../../../services/blocking/delete'; import { apLogger } from '../../logger'; +import { IRemoteUser } from '../../../../models/entities/user'; +import { Users } from '../../../../models'; const logger = apLogger; @@ -18,11 +18,9 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise => { return null; } - const blockee = await User.findOne({ - _id: new mongo.ObjectID(id.split('/').pop()) - }); + const blockee = await Users.findOne(id.split('/').pop()); - if (blockee === null) { + if (blockee == null) { throw new Error('blockee not found'); } diff --git a/src/remote/activitypub/kernel/undo/follow.ts b/src/remote/activitypub/kernel/undo/follow.ts index af06aa5b31..cc63a740b1 100644 --- a/src/remote/activitypub/kernel/undo/follow.ts +++ b/src/remote/activitypub/kernel/undo/follow.ts @@ -1,11 +1,9 @@ -import * as mongo from 'mongodb'; -import User, { IRemoteUser } from '../../../../models/user'; import config from '../../../../config'; import unfollow from '../../../../services/following/delete'; import cancelRequest from '../../../../services/following/requests/cancel'; import { IFollow } from '../../type'; -import FollowRequest from '../../../../models/follow-request'; -import Following from '../../../../models/following'; +import { IRemoteUser } from '../../../../models/entities/user'; +import { Users, FollowRequests, Followings } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; @@ -14,11 +12,9 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise => { return null; } - const followee = await User.findOne({ - _id: new mongo.ObjectID(id.split('/').pop()) - }); + const followee = await Users.findOne(id.split('/').pop()); - if (followee === null) { + if (followee == null) { throw new Error('followee not found'); } @@ -26,14 +22,14 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise => { throw new Error('フォロー解除しようとしているユーザーはローカルユーザーではありません'); } - const req = await FollowRequest.findOne({ - followerId: actor._id, - followeeId: followee._id + const req = await FollowRequests.findOne({ + followerId: actor.id, + followeeId: followee.id }); - const following = await Following.findOne({ - followerId: actor._id, - followeeId: followee._id + const following = await Followings.findOne({ + followerId: actor.id, + followeeId: followee.id }); if (req) { diff --git a/src/remote/activitypub/kernel/undo/index.ts b/src/remote/activitypub/kernel/undo/index.ts index 80b44fae04..6376ab93a8 100644 --- a/src/remote/activitypub/kernel/undo/index.ts +++ b/src/remote/activitypub/kernel/undo/index.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { IUndo, IFollow, IBlock, ILike } from '../../type'; import unfollow from './follow'; import unblock from './block'; diff --git a/src/remote/activitypub/kernel/undo/like.ts b/src/remote/activitypub/kernel/undo/like.ts index b324ec854c..f337a0173e 100644 --- a/src/remote/activitypub/kernel/undo/like.ts +++ b/src/remote/activitypub/kernel/undo/like.ts @@ -1,8 +1,7 @@ -import * as mongo from 'mongodb'; -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { ILike } from '../../type'; -import Note from '../../../../models/note'; import deleteReaction from '../../../../services/note/reaction/delete'; +import { Notes } from '../../../../models'; /** * Process Undo.Like activity @@ -10,10 +9,10 @@ import deleteReaction from '../../../../services/note/reaction/delete'; export default async (actor: IRemoteUser, activity: ILike): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; - const noteId = new mongo.ObjectID(id.split('/').pop()); + const noteId = id.split('/').pop(); - const note = await Note.findOne({ _id: noteId }); - if (note === null) { + const note = await Notes.findOne(noteId); + if (note == null) { throw 'note not found'; } diff --git a/src/remote/activitypub/kernel/update/index.ts b/src/remote/activitypub/kernel/update/index.ts index 49b730391a..b8dff73395 100644 --- a/src/remote/activitypub/kernel/update/index.ts +++ b/src/remote/activitypub/kernel/update/index.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/entities/user'; import { IUpdate, IObject } from '../../type'; import { apLogger } from '../../logger'; import { updateQuestion } from '../../models/question'; diff --git a/src/remote/activitypub/misc/get-note-html.ts b/src/remote/activitypub/misc/get-note-html.ts index 967ee65544..dba915fee9 100644 --- a/src/remote/activitypub/misc/get-note-html.ts +++ b/src/remote/activitypub/misc/get-note-html.ts @@ -1,9 +1,9 @@ -import { INote } from '../../../models/note'; +import { Note } from '../../../models/entities/note'; import { toHtml } from '../../../mfm/toHtml'; import { parse } from '../../../mfm/parse'; -export default function(note: INote) { - let html = toHtml(parse(note.text), note.mentionedRemoteUsers); +export default function(note: Note) { + let html = toHtml(parse(note.text), JSON.parse(note.mentionedRemoteUsers)); if (html == null) html = '

.

'; return html; diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts index bd97d13d27..87095acd88 100644 --- a/src/remote/activitypub/models/image.ts +++ b/src/remote/activitypub/models/image.ts @@ -1,16 +1,17 @@ import uploadFromUrl from '../../../services/drive/upload-from-url'; -import { IRemoteUser } from '../../../models/user'; -import DriveFile, { IDriveFile } from '../../../models/drive-file'; +import { IRemoteUser } from '../../../models/entities/user'; import Resolver from '../resolver'; import fetchMeta from '../../../misc/fetch-meta'; import { apLogger } from '../logger'; +import { DriveFile } from '../../../models/entities/drive-file'; +import { DriveFiles } from '../../../models'; const logger = apLogger; /** * Imageを作成します。 */ -export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function createImage(actor: IRemoteUser, value: any): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { return null; @@ -39,18 +40,16 @@ export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function resolveImage(actor: IRemoteUser, value: any): Promise { // TODO // リモートサーバーからフェッチしてきて登録 diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index 6251621527..cd587c51cf 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -1,26 +1,27 @@ -import * as mongo from 'mongodb'; import * as promiseLimit from 'promise-limit'; import config from '../../../config'; import Resolver from '../resolver'; -import Note, { INote } from '../../../models/note'; import post from '../../../services/note/create'; -import { INote as INoteActivityStreamsObject, IObject } from '../type'; import { resolvePerson, updatePerson } from './person'; import { resolveImage } from './image'; -import { IRemoteUser, IUser } from '../../../models/user'; +import { IRemoteUser, User } from '../../../models/entities/user'; import { fromHtml } from '../../../mfm/fromHtml'; -import Emoji, { IEmoji } from '../../../models/emoji'; import { ITag, extractHashtags } from './tag'; import { toUnicode } from 'punycode'; import { unique, concat, difference } from '../../../prelude/array'; import { extractPollFromQuestion } from './question'; import vote from '../../../services/note/polls/vote'; import { apLogger } from '../logger'; -import { IDriveFile } from '../../../models/drive-file'; +import { DriveFile } from '../../../models/entities/drive-file'; import { deliverQuestionUpdate } from '../../../services/note/polls/update'; -import Instance from '../../../models/instance'; import { extractDbHost } from '../../../misc/convert-host'; +import { Notes, Emojis, Polls } from '../../../models'; +import { Note } from '../../../models/entities/note'; +import { IObject, INote } from '../type'; +import { Emoji } from '../../../models/entities/emoji'; +import { genId } from '../../../misc/gen-id'; +import fetchMeta from '../../../misc/fetch-meta'; const logger = apLogger; @@ -29,17 +30,17 @@ const logger = apLogger; * * Misskeyに対象のNoteが登録されていればそれを返します。 */ -export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise { +export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { - const id = new mongo.ObjectID(uri.split('/').pop()); - return await Note.findOne({ _id: id }); + const id = uri.split('/').pop(); + return await Notes.findOne(id); } //#region このサーバーに既に登録されていたらそれを返す - const exist = await Note.findOne({ uri }); + const exist = await Notes.findOne({ uri }); if (exist) { return exist; @@ -52,7 +53,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P /** * Noteを作成します。 */ -export async function createNote(value: any, resolver?: Resolver, silent = false): Promise { +export async function createNote(value: any, resolver?: Resolver, silent = false): Promise { if (resolver == null) resolver = new Resolver(); const object: any = await resolver.resolve(value); @@ -68,7 +69,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false return null; } - const note: INoteActivityStreamsObject = object; + const note: INote = object; logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); @@ -87,7 +88,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false note.cc = note.cc == null ? [] : typeof note.cc == 'string' ? [note.cc] : note.cc; let visibility = 'public'; - let visibleUsers: IUser[] = []; + let visibleUsers: User[] = []; if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) { if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) { visibility = 'home'; @@ -113,12 +114,12 @@ export async function createNote(value: any, resolver?: Resolver, silent = false note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; const files = note.attachment .map(attach => attach.sensitive = note.sensitive) - ? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x)) as Promise))) + ? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x)) as Promise))) .filter(image => image != null) : []; // リプライ - const reply: INote = note.inReplyTo + const reply: Note = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver).catch(e => { // 4xxの場合はリプライしてないことにする if (e.statusCode >= 400 && e.statusCode < 500) { @@ -131,7 +132,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false : null; // 引用 - let quote: INote; + let quote: Note; if (note._misskey_quote && typeof note._misskey_quote == 'string') { quote = await resolveNote(note._misskey_quote).catch(e => { @@ -151,22 +152,23 @@ export async function createNote(value: any, resolver?: Resolver, silent = false const text = note._misskey_content || fromHtml(note.content); // vote - if (reply && reply.poll) { + if (reply && reply.hasPoll) { + const poll = await Polls.findOne({ noteId: reply.id }); const tryCreateVote = async (name: string, index: number): Promise => { - if (reply.poll.expiresAt && Date.now() > new Date(reply.poll.expiresAt).getTime()) { + if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); } else if (index >= 0) { logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); await vote(actor, reply, index); // リモートフォロワーにUpdate配信 - deliverQuestionUpdate(reply._id); + deliverQuestionUpdate(reply.id); } return null; }; if (note.name) { - return await tryCreateVote(note.name, reply.poll.choices.findIndex(x => x.text === note.name)); + return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); } // 後方互換性のため @@ -181,7 +183,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false const emojis = await extractEmojis(note.tag, actor.host).catch(e => { logger.info(`extractEmojis: ${e}`); - return [] as IEmoji[]; + return [] as Emoji[]; }); const apEmojis = emojis.map(emoji => emoji.name); @@ -222,13 +224,13 @@ export async function createNote(value: any, resolver?: Resolver, silent = false * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { +export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく - const instance = await Instance.findOne({ host: extractDbHost(uri) }); - if (instance && instance.isBlocked) throw { statusCode: 451 }; + const meta = await fetchMeta(); + if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 }; //#region このサーバーに既に登録されていたらそれを返す const exist = await fetchNote(uri); @@ -255,7 +257,7 @@ export async function extractEmojis(tags: ITag[], host_: string) { eomjiTags.map(async tag => { const name = tag.name.replace(/^:/, '').replace(/:$/, ''); - const exists = await Emoji.findOne({ + const exists = await Emojis.findOne({ host, name }); @@ -263,31 +265,37 @@ export async function extractEmojis(tags: ITag[], host_: string) { if (exists) { if ((tag.updated != null && exists.updatedAt == null) || (tag.id != null && exists.uri == null) - || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)) { - return await Emoji.findOneAndUpdate({ - host, - name, - }, { - $set: { - uri: tag.id, - url: tag.icon.url, - updatedAt: new Date(tag.updated), - } - }); + || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) + ) { + await Emojis.update({ + host, + name, + }, { + uri: tag.id, + url: tag.icon.url, + updatedAt: new Date(tag.updated), + }); + + return await Emojis.findOne({ + host, + name + }); } + return exists; } logger.info(`register emoji host=${host}, name=${name}`); - return await Emoji.insert({ + return await Emojis.save({ + id: genId(), host, name, uri: tag.id, url: tag.icon.url, updatedAt: tag.updated ? new Date(tag.updated) : undefined, aliases: [] - }); + } as Emoji); }) ); } @@ -298,7 +306,7 @@ async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: strin const limit = promiseLimit(2); const users = await Promise.all( - uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise) + uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise) ); return users.filter(x => x != null); diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index d27c937988..51a9efa10b 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -1,29 +1,29 @@ -import * as mongo from 'mongodb'; import * as promiseLimit from 'promise-limit'; import { toUnicode } from 'punycode'; import config from '../../../config'; -import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user'; import Resolver from '../resolver'; import { resolveImage } from './image'; import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'; -import { IDriveFile } from '../../../models/drive-file'; -import Meta from '../../../models/meta'; +import { DriveFile } from '../../../models/entities/drive-file'; import { fromHtml } from '../../../mfm/fromHtml'; -import usersChart from '../../../services/chart/users'; -import instanceChart from '../../../services/chart/instance'; import { URL } from 'url'; import { resolveNote, extractEmojis } from './note'; import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc'; -import Instance from '../../../models/instance'; -import getDriveFileUrl from '../../../misc/get-drive-file-url'; -import { IEmoji } from '../../../models/emoji'; import { ITag, extractHashtags } from './tag'; -import Following from '../../../models/following'; import { IIdentifier } from './identifier'; import { apLogger } from '../logger'; -import { INote } from '../../../models/note'; +import { Note } from '../../../models/entities/note'; import { updateHashtag } from '../../../services/update-hashtag'; +import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserServiceLinkings, UserPublickeys } from '../../../models'; +import { User, IRemoteUser } from '../../../models/entities/user'; +import { Emoji } from '../../../models/entities/emoji'; +import { UserNotePining } from '../../../models/entities/user-note-pinings'; +import { genId } from '../../../misc/gen-id'; +import { UserServiceLinking } from '../../../models/entities/user-service-linking'; +import { instanceChart, usersChart } from '../../../services/chart'; +import { UserPublickey } from '../../../models/entities/user-publickey'; +import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error'; const logger = apLogger; /** @@ -50,11 +50,11 @@ function validatePerson(x: any, uri: string) { return new Error('invalid person: inbox is not a string'); } - if (!validateUsername(x.preferredUsername, true)) { + if (!Users.validateUsername(x.preferredUsername, true)) { return new Error('invalid person: invalid username'); } - if (!isValidName(x.name == '' ? null : x.name)) { + if (!Users.isValidName(x.name == '' ? null : x.name)) { return new Error('invalid person: invalid name'); } @@ -84,17 +84,17 @@ function validatePerson(x: any, uri: string) { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw 'uri is not string'; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { - const id = new mongo.ObjectID(uri.split('/').pop()); - return await User.findOne({ _id: id }); + const id = uri.split('/').pop(); + return await Users.findOne(id); } //#region このサーバーに既に登録されていたらそれを返す - const exist = await User.findOne({ uri }); + const exist = await Users.findOne({ uri }); if (exist) { return exist; @@ -107,7 +107,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function createPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw 'uri is not string'; if (resolver == null) resolver = new Resolver(); @@ -124,21 +124,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, - () => undefined - ), - resolver.resolve(person.following).then( - resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, - () => undefined - ), - resolver.resolve(person.outbox).then( - resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, - () => undefined - ) - ]); - const host = toUnicode(new URL(object.id).hostname.toLowerCase()); const { fields, services } = analyzeAttachments(person.attachment); @@ -150,24 +135,18 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise) as IRemoteUser; + + await UserPublickeys.save({ + id: genId(), + userId: user.id, + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem + } as UserPublickey); + + await UserServiceLinkings.save({ + id: genId(), + userId: user.id, + } as UserServiceLinking); } catch (e) { // duplicate key error - if (e.code === 11000) { + if (isDuplicateKeyValueError(e)) { throw new Error('already registered'); } @@ -190,33 +181,25 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { - Instance.update({ _id: i._id }, { - $inc: { - usersCount: 1 - } - }); - + Instances.increment({ id: i.id }, 'usersCount', 1); instanceChart.newUser(i.host); }); - //#region Increment users count - Meta.update({}, { - $inc: { - 'stats.usersCount': 1 - } - }, { upsert: true }); - usersChart.update(user, true); - //#endregion // ハッシュタグ更新 for (const tag of tags) updateHashtag(user, tag, true, true); for (const tag of (user.tags || []).filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false); //#region アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all([ + const [avatar, banner] = (await Promise.all([ person.icon, person.image ].map(img => @@ -225,22 +208,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise null) ))); - const avatarId = avatar ? avatar._id : null; - const bannerId = banner ? banner._id : null; - const avatarUrl = getDriveFileUrl(avatar, true); - const bannerUrl = getDriveFileUrl(banner, false); - const avatarColor = avatar && avatar.metadata.properties.avgColor ? avatar.metadata.properties.avgColor : null; - const bannerColor = banner && avatar.metadata.properties.avgColor ? banner.metadata.properties.avgColor : null; - - await User.update({ _id: user._id }, { - $set: { - avatarId, - bannerId, - avatarUrl, - bannerUrl, - avatarColor, - bannerColor - } + const avatarId = avatar ? avatar.id : null; + const bannerId = banner ? banner.id : null; + const avatarUrl = DriveFiles.getPublicUrl(avatar); + const bannerUrl = DriveFiles.getPublicUrl(banner); + const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null; + const bannerColor = banner && avatar.properties.avgColor ? banner.properties.avgColor : null; + + await Users.update(user.id, { + avatarId, + bannerId, + avatarUrl, + bannerUrl, + avatarColor, + bannerColor }); user.avatarId = avatarId; @@ -254,19 +235,17 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { logger.info(`extractEmojis: ${e}`); - return [] as IEmoji[]; + return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); - await User.update({ _id: user._id }, { - $set: { - emojis: emojiNames - } + await Users.update(user.id, { + emojis: emojiNames }); //#endregion - await updateFeatured(user._id).catch(err => logger.error(err)); + await updateFeatured(user.id).catch(err => logger.error(err)); return user; } @@ -287,7 +266,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje } //#region このサーバーに既に登録されているか - const exist = await User.findOne({ uri }) as IRemoteUser; + const exist = await Users.findOne({ uri }) as IRemoteUser; if (exist == null) { return; @@ -295,10 +274,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje //#endregion // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する - await User.update({ _id: exist._id }, { - $set: { - lastFetchedAt: new Date(), - }, + await Users.update(exist.id, { + lastFetchedAt: new Date(), }); if (resolver == null) resolver = new Resolver(); @@ -315,23 +292,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje logger.info(`Updating the Person: ${person.id}`); - const [followersCount = 0, followingCount = 0, notesCount = 0] = await Promise.all([ - resolver.resolve(person.followers).then( - resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, - () => undefined - ), - resolver.resolve(person.following).then( - resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, - () => undefined - ), - resolver.resolve(person.outbox).then( - resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, - () => undefined - ) - ]); - // アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all([ + const [avatar, banner] = (await Promise.all([ person.icon, person.image ].map(img => @@ -343,7 +305,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje // カスタム絵文字取得 const emojis = await extractEmojis(person.tag, exist.host).catch(e => { logger.info(`extractEmojis: ${e}`); - return [] as IEmoji[]; + return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); @@ -359,40 +321,45 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje featured: person.featured, emojis: emojiNames, description: fromHtml(person.summary), - followersCount, - followingCount, - notesCount, name: person.name, url: person.url, endpoints: person.endpoints, fields, - ...services, tags, isBot: object.type == 'Service', isCat: (person as any).isCat === true, isLocked: person.manuallyApprovesFollowers, - createdAt: Date.parse(person.published) || null, - publicKey: { - id: person.publicKey.id, - publicKeyPem: person.publicKey.publicKeyPem - }, - } as any; + createdAt: new Date(Date.parse(person.published)) || null, + } as Partial; if (avatar) { - updates.avatarId = avatar._id; - updates.avatarUrl = getDriveFileUrl(avatar, true); - updates.avatarColor = avatar.metadata.properties.avgColor ? avatar.metadata.properties.avgColor : null; + updates.avatarId = avatar.id; + updates.avatarUrl = DriveFiles.getPublicUrl(avatar); + updates.avatarColor = avatar.properties.avgColor ? avatar.properties.avgColor : null; } if (banner) { - updates.bannerId = banner._id; - updates.bannerUrl = getDriveFileUrl(banner, true); - updates.bannerColor = banner.metadata.properties.avgColor ? banner.metadata.properties.avgColor : null; + updates.bannerId = banner.id; + updates.bannerUrl = DriveFiles.getPublicUrl(banner); + updates.bannerColor = banner.properties.avgColor ? banner.properties.avgColor : null; } // Update user - await User.update({ _id: exist._id }, { - $set: updates + await Users.update(exist.id, updates); + + await UserPublickeys.update({ userId: exist.id }, { + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem + }); + + await UserServiceLinkings.update({ userId: exist.id }, { + twitterUserId: services.twitter.userId, + twitterScreenName: services.twitter.screenName, + githubId: services.github.id, + githubLogin: services.github.login, + discordId: services.discord.id, + discordUsername: services.discord.username, + discordDiscriminator: services.discord.discriminator, }); // ハッシュタグ更新 @@ -400,17 +367,13 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje for (const tag of (exist.tags || []).filter(x => !tags.includes(x))) updateHashtag(exist, tag, true, false); // 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする - await Following.update({ - followerId: exist._id - }, { - $set: { - '_follower.sharedInbox': person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined) - } + await Followings.update({ + followerId: exist.id }, { - multi: true + followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined) }); - await updateFeatured(exist._id).catch(err => logger.error(err)); + await updateFeatured(exist.id).catch(err => logger.error(err)); } /** @@ -419,7 +382,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise { +export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw 'uri is not string'; //#region このサーバーに既に登録されていたらそれを返す @@ -492,9 +455,9 @@ export function analyzeAttachments(attachments: ITag[]) { return { fields, services }; } -export async function updateFeatured(userId: mongo.ObjectID) { - const user = await User.findOne({ _id: userId }); - if (!isRemoteUser(user)) return; +export async function updateFeatured(userId: User['id']) { + const user = await Users.findOne(userId); + if (!Users.isRemoteUser(user)) return; if (!user.featured) return; logger.info(`Updating the featured: ${user.uri}`); @@ -515,11 +478,14 @@ export async function updateFeatured(userId: mongo.ObjectID) { const featuredNotes = await Promise.all(items .filter(item => item.type === 'Note') .slice(0, 5) - .map(item => limit(() => resolveNote(item, resolver)) as Promise)); - - await User.update({ _id: user._id }, { - $set: { - pinnedNoteIds: featuredNotes.filter(note => note != null).map(note => note._id) - } - }); + .map(item => limit(() => resolveNote(item, resolver)) as Promise)); + + for (const note of featuredNotes.filter(note => note != null)) { + UserNotePinings.save({ + id: genId(), + createdAt: new Date(), + userId: user.id, + noteId: note.id + } as UserNotePining); + } } diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index c073684349..a5091a6d96 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -1,8 +1,9 @@ import config from '../../../config'; -import Note, { IChoice, IPoll } from '../../../models/note'; import Resolver from '../resolver'; import { IQuestion } from '../type'; import { apLogger } from '../logger'; +import { Notes, Polls } from '../../../models'; +import { IPoll } from '../../../models/entities/poll'; export async function extractPollFromQuestion(source: string | IQuestion): Promise { const question = typeof source === 'string' ? await new Resolver().resolve(source) as IQuestion : source; @@ -14,14 +15,14 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi } const choices = question[multiple ? 'anyOf' : 'oneOf'] - .map((x, i) => ({ - id: i, - text: x.name, - votes: x.replies && x.replies.totalItems || x._misskey_votes || 0, - } as IChoice)); + .map((x, i) => x.name); + + const votes = question[multiple ? 'anyOf' : 'oneOf'] + .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); return { choices, + votes, multiple, expiresAt }; @@ -39,9 +40,11 @@ export async function updateQuestion(value: any) { if (uri.startsWith(config.url + '/')) throw 'uri points local'; //#region このサーバーに既に登録されているか - const note = await Note.findOne({ uri }); - + const note = await Notes.findOne({ uri }); if (note == null) throw 'Question is not registed'; + + const poll = await Polls.findOne({ noteId: note.id }); + if (poll == null) throw 'Question is not registed'; //#endregion // resolve new Question object @@ -52,27 +55,25 @@ export async function updateQuestion(value: any) { if (question.type !== 'Question') throw 'object is not a Question'; const apChoices = question.oneOf || question.anyOf; - const dbChoices = note.poll.choices; let changed = false; - for (const db of dbChoices) { - const oldCount = db.votes; - const newCount = apChoices.filter(ap => ap.name === db.text)[0].replies.totalItems; + for (const choice of poll.choices) { + const oldCount = poll.votes[poll.choices.indexOf(choice)]; + const newCount = apChoices.filter(ap => ap.name === choice)[0].replies.totalItems; if (oldCount != newCount) { changed = true; - db.votes = newCount; + poll.votes[poll.choices.indexOf(choice)] = newCount; } } - await Note.update({ - _id: note._id - }, { - $set: { - 'poll.choices': dbChoices, - updatedAt: new Date(), - } + await Notes.update(note.id, { + updatedAt: new Date(), + }); + + await Polls.update(poll.id, { + votes: poll.votes }); return changed; diff --git a/src/remote/activitypub/perform.ts b/src/remote/activitypub/perform.ts index 2e4f53adf5..425adaec96 100644 --- a/src/remote/activitypub/perform.ts +++ b/src/remote/activitypub/perform.ts @@ -1,5 +1,5 @@ import { Object } from './type'; -import { IRemoteUser } from '../../models/user'; +import { IRemoteUser } from '../../models/entities/user'; import kernel from './kernel'; export default async (actor: IRemoteUser, activity: Object): Promise => { diff --git a/src/remote/activitypub/renderer/accept.ts b/src/remote/activitypub/renderer/accept.ts index fdbdff3f12..21b4629074 100644 --- a/src/remote/activitypub/renderer/accept.ts +++ b/src/remote/activitypub/renderer/accept.ts @@ -1,8 +1,8 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default (object: any, user: ILocalUser) => ({ type: 'Accept', - actor: `${config.url}/users/${user._id}`, + actor: `${config.url}/users/${user.id}`, object }); diff --git a/src/remote/activitypub/renderer/add.ts b/src/remote/activitypub/renderer/add.ts index 4d6fe392aa..46f937f61d 100644 --- a/src/remote/activitypub/renderer/add.ts +++ b/src/remote/activitypub/renderer/add.ts @@ -1,9 +1,9 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default (user: ILocalUser, target: any, object: any) => ({ type: 'Add', - actor: `${config.url}/users/${user._id}`, + actor: `${config.url}/users/${user.id}`, target, object }); diff --git a/src/remote/activitypub/renderer/announce.ts b/src/remote/activitypub/renderer/announce.ts index f6f2f9bdcd..11e7be449b 100644 --- a/src/remote/activitypub/renderer/announce.ts +++ b/src/remote/activitypub/renderer/announce.ts @@ -1,7 +1,7 @@ import config from '../../../config'; -import { INote } from '../../../models/note'; +import { Note } from '../../../models/entities/note'; -export default (object: any, note: INote) => { +export default (object: any, note: Note) => { const attributedTo = `${config.url}/users/${note.userId}`; let to: string[] = []; @@ -18,7 +18,7 @@ export default (object: any, note: INote) => { } return { - id: `${config.url}/notes/${note._id}/activity`, + id: `${config.url}/notes/${note.id}/activity`, actor: `${config.url}/users/${note.userId}`, type: 'Announce', published: note.createdAt.toISOString(), diff --git a/src/remote/activitypub/renderer/block.ts b/src/remote/activitypub/renderer/block.ts index 694f3a1418..946c45a813 100644 --- a/src/remote/activitypub/renderer/block.ts +++ b/src/remote/activitypub/renderer/block.ts @@ -1,8 +1,8 @@ import config from '../../../config'; -import { ILocalUser, IRemoteUser } from '../../../models/user'; +import { ILocalUser, IRemoteUser } from '../../../models/entities/user'; export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({ type: 'Block', - actor: `${config.url}/users/${blocker._id}`, + actor: `${config.url}/users/${blocker.id}`, object: blockee.uri }); diff --git a/src/remote/activitypub/renderer/create.ts b/src/remote/activitypub/renderer/create.ts index 1ee1418fce..e1fc0515c8 100644 --- a/src/remote/activitypub/renderer/create.ts +++ b/src/remote/activitypub/renderer/create.ts @@ -1,9 +1,9 @@ import config from '../../../config'; -import { INote } from '../../../models/note'; +import { Note } from '../../../models/entities/note'; -export default (object: any, note: INote) => { +export default (object: any, note: Note) => { const activity = { - id: `${config.url}/notes/${note._id}/activity`, + id: `${config.url}/notes/${note.id}/activity`, actor: `${config.url}/users/${note.userId}`, type: 'Create', published: note.createdAt.toISOString(), diff --git a/src/remote/activitypub/renderer/delete.ts b/src/remote/activitypub/renderer/delete.ts index e090e1c886..a98c97e6e9 100644 --- a/src/remote/activitypub/renderer/delete.ts +++ b/src/remote/activitypub/renderer/delete.ts @@ -1,8 +1,8 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default (object: any, user: ILocalUser) => ({ type: 'Delete', - actor: `${config.url}/users/${user._id}`, + actor: `${config.url}/users/${user.id}`, object }); diff --git a/src/remote/activitypub/renderer/document.ts b/src/remote/activitypub/renderer/document.ts index 17721e9417..4f6ea8c4ee 100644 --- a/src/remote/activitypub/renderer/document.ts +++ b/src/remote/activitypub/renderer/document.ts @@ -1,8 +1,8 @@ -import { IDriveFile } from '../../../models/drive-file'; -import getDriveFileUrl from '../../../misc/get-drive-file-url'; +import { DriveFile } from '../../../models/entities/drive-file'; +import { DriveFiles } from '../../../models'; -export default (file: IDriveFile) => ({ +export default (file: DriveFile) => ({ type: 'Document', - mediaType: file.contentType, - url: getDriveFileUrl(file) + mediaType: file.type, + url: DriveFiles.getPublicUrl(file) }); diff --git a/src/remote/activitypub/renderer/emoji.ts b/src/remote/activitypub/renderer/emoji.ts index 1a05b4e89e..947a96df37 100644 --- a/src/remote/activitypub/renderer/emoji.ts +++ b/src/remote/activitypub/renderer/emoji.ts @@ -1,7 +1,7 @@ -import { IEmoji } from '../../../models/emoji'; import config from '../../../config'; +import { Emoji } from '../../../models/entities/emoji'; -export default (emoji: IEmoji) => ({ +export default (emoji: Emoji) => ({ id: `${config.url}/emojis/${emoji.name}`, type: 'Emoji', name: `:${emoji.name}:`, diff --git a/src/remote/activitypub/renderer/follow-user.ts b/src/remote/activitypub/renderer/follow-user.ts index 9a488d392b..9446be3c86 100644 --- a/src/remote/activitypub/renderer/follow-user.ts +++ b/src/remote/activitypub/renderer/follow-user.ts @@ -1,16 +1,12 @@ import config from '../../../config'; -import * as mongo from 'mongodb'; -import User, { isLocalUser } from '../../../models/user'; +import { Users } from '../../../models'; +import { User } from '../../../models/entities/user'; /** * Convert (local|remote)(Follower|Followee)ID to URL * @param id Follower|Followee ID */ -export default async function renderFollowUser(id: mongo.ObjectID): Promise { - - const user = await User.findOne({ - _id: id - }); - - return isLocalUser(user) ? `${config.url}/users/${user._id}` : user.uri; +export default async function renderFollowUser(id: User['id']): Promise { + const user = await Users.findOne(id); + return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri; } diff --git a/src/remote/activitypub/renderer/follow.ts b/src/remote/activitypub/renderer/follow.ts index 98d4cdd020..400b15ec7b 100644 --- a/src/remote/activitypub/renderer/follow.ts +++ b/src/remote/activitypub/renderer/follow.ts @@ -1,11 +1,12 @@ import config from '../../../config'; -import { IUser, isLocalUser } from '../../../models/user'; +import { User } from '../../../models/entities/user'; +import { Users } from '../../../models'; -export default (follower: IUser, followee: IUser, requestId?: string) => { +export default (follower: User, followee: User, requestId?: string) => { const follow = { type: 'Follow', - actor: isLocalUser(follower) ? `${config.url}/users/${follower._id}` : follower.uri, - object: isLocalUser(followee) ? `${config.url}/users/${followee._id}` : followee.uri + actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri, + object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri } as any; if (requestId) follow.id = requestId; diff --git a/src/remote/activitypub/renderer/image.ts b/src/remote/activitypub/renderer/image.ts index ec637b9521..ce98f98c62 100644 --- a/src/remote/activitypub/renderer/image.ts +++ b/src/remote/activitypub/renderer/image.ts @@ -1,8 +1,8 @@ -import { IDriveFile } from '../../../models/drive-file'; -import getDriveFileUrl from '../../../misc/get-drive-file-url'; +import { DriveFile } from '../../../models/entities/drive-file'; +import { DriveFiles } from '../../../models'; -export default (file: IDriveFile) => ({ +export default (file: DriveFile) => ({ type: 'Image', - url: getDriveFileUrl(file), - sensitive: file.metadata.isSensitive + url: DriveFiles.getPublicUrl(file), + sensitive: file.isSensitive }); diff --git a/src/remote/activitypub/renderer/key.ts b/src/remote/activitypub/renderer/key.ts index 0d5e52557c..fb5975a6c4 100644 --- a/src/remote/activitypub/renderer/key.ts +++ b/src/remote/activitypub/renderer/key.ts @@ -1,10 +1,11 @@ +import { createPublicKey } from 'crypto'; import config from '../../../config'; -import { extractPublic } from '../../../crypto_key'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; +import { UserKeypair } from '../../../models/entities/user-keypair'; -export default (user: ILocalUser) => ({ - id: `${config.url}/users/${user._id}/publickey`, +export default (user: ILocalUser, key: UserKeypair) => ({ + id: `${config.url}/users/${user.id}/publickey`, type: 'Key', - owner: `${config.url}/users/${user._id}`, - publicKeyPem: extractPublic(user.keypair) + owner: `${config.url}/users/${user.id}`, + publicKeyPem: createPublicKey(key.keyPem) }); diff --git a/src/remote/activitypub/renderer/like.ts b/src/remote/activitypub/renderer/like.ts index 523cb4f1ad..01f10ec0a9 100644 --- a/src/remote/activitypub/renderer/like.ts +++ b/src/remote/activitypub/renderer/like.ts @@ -1,10 +1,10 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; -import { INote } from '../../../models/note'; +import { ILocalUser } from '../../../models/entities/user'; +import { Note } from '../../../models/entities/note'; -export default (user: ILocalUser, note: INote, reaction: string) => ({ +export default (user: ILocalUser, note: Note, reaction: string) => ({ type: 'Like', - actor: `${config.url}/users/${user._id}`, - object: note.uri ? note.uri : `${config.url}/notes/${note._id}`, + actor: `${config.url}/users/${user.id}`, + object: note.uri ? note.uri : `${config.url}/notes/${note.id}`, _misskey_reaction: reaction }); diff --git a/src/remote/activitypub/renderer/mention.ts b/src/remote/activitypub/renderer/mention.ts index 8d12e6d8bf..889be5d85d 100644 --- a/src/remote/activitypub/renderer/mention.ts +++ b/src/remote/activitypub/renderer/mention.ts @@ -1,8 +1,9 @@ -import { IUser, isRemoteUser } from '../../../models/user'; import config from '../../../config'; +import { User, ILocalUser } from '../../../models/entities/user'; +import { Users } from '../../../models'; -export default (mention: IUser) => ({ +export default (mention: User) => ({ type: 'Mention', - href: isRemoteUser(mention) ? mention.uri : `${config.url}/@${mention.username}`, - name: isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${mention.username}`, + href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/@${(mention as ILocalUser).username}`, + name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`, }); diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index 8b349526e1..5b36366b28 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -3,29 +3,27 @@ import renderHashtag from './hashtag'; import renderMention from './mention'; import renderEmoji from './emoji'; import config from '../../../config'; -import DriveFile, { IDriveFile } from '../../../models/drive-file'; -import Note, { INote } from '../../../models/note'; -import User from '../../../models/user'; import toHtml from '../misc/get-note-html'; -import Emoji, { IEmoji } from '../../../models/emoji'; - -export default async function renderNote(note: INote, dive = true): Promise { - const promisedFiles: Promise = note.fileIds - ? DriveFile.find({ _id: { $in: note.fileIds } }) +import { Note, IMentionedRemoteUsers } from '../../../models/entities/note'; +import { DriveFile } from '../../../models/entities/drive-file'; +import { DriveFiles, Notes, Users, Emojis, Polls } from '../../../models'; +import { In } from 'typeorm'; +import { Emoji } from '../../../models/entities/emoji'; +import { Poll } from '../../../models/entities/poll'; + +export default async function renderNote(note: Note, dive = true): Promise { + const promisedFiles: Promise = note.fileIds.length > 1 + ? DriveFiles.find({ id: In(note.fileIds) }) : Promise.resolve([]); let inReplyTo; - let inReplyToNote: INote; + let inReplyToNote: Note; if (note.replyId) { - inReplyToNote = await Note.findOne({ - _id: note.replyId, - }); + inReplyToNote = await Notes.findOne(note.replyId); if (inReplyToNote !== null) { - const inReplyToUser = await User.findOne({ - _id: inReplyToNote.userId, - }); + const inReplyToUser = await Users.findOne(inReplyToNote.userId); if (inReplyToUser !== null) { if (inReplyToNote.uri) { @@ -34,7 +32,7 @@ export default async function renderNote(note: INote, dive = true): Promise if (dive) { inReplyTo = await renderNote(inReplyToNote, false); } else { - inReplyTo = `${config.url}/notes/${inReplyToNote._id}`; + inReplyTo = `${config.url}/notes/${inReplyToNote.id}`; } } } @@ -46,24 +44,20 @@ export default async function renderNote(note: INote, dive = true): Promise let quote; if (note.renoteId) { - const renote = await Note.findOne({ - _id: note.renoteId, - }); + const renote = await Notes.findOne(note.renoteId); if (renote) { - quote = renote.uri ? renote.uri : `${config.url}/notes/${renote._id}`; + quote = renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`; } } - const user = await User.findOne({ - _id: note.userId + const user = await Users.findOne({ + id: note.userId }); - const attributedTo = `${config.url}/users/${user._id}`; + const attributedTo = `${config.url}/users/${user.id}`; - const mentions = note.mentionedRemoteUsers && note.mentionedRemoteUsers.length > 0 - ? note.mentionedRemoteUsers.map(x => x.uri) - : []; + const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); let to: string[] = []; let cc: string[] = []; @@ -81,10 +75,8 @@ export default async function renderNote(note: INote, dive = true): Promise to = mentions; } - const mentionedUsers = note.mentions ? await User.find({ - _id: { - $in: note.mentions - } + const mentionedUsers = note.mentions.length > 0 ? await Users.find({ + id: In(note.mentions) }) : []; const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag)); @@ -93,23 +85,28 @@ export default async function renderNote(note: INote, dive = true): Promise const files = await promisedFiles; let text = note.text; + let poll: Poll; + + if (note.hasPoll) { + poll = await Polls.findOne({ noteId: note.id }); + } let question: string; - if (note.poll != null) { + if (poll) { if (text == null) text = ''; - const url = `${config.url}/notes/${note._id}`; + const url = `${config.url}/notes/${note.id}`; // TODO: i18n text += `\n[リモートで結果を表示](${url})`; - question = `${config.url}/questions/${note._id}`; + question = `${config.url}/questions/${note.id}`; } let apText = text; if (apText == null) apText = ''; // Provides choices as text for AP - if (note.poll != null) { - const cs = note.poll.choices.map(c => `${c.id}: ${c.text}`); + if (poll) { + const cs = poll.choices.map((c, i) => `${i}: ${c}`); apText += '\n----------------------------------------\n'; apText += cs.join('\n'); apText += '\n----------------------------------------\n'; @@ -135,31 +132,25 @@ export default async function renderNote(note: INote, dive = true): Promise ...apemojis, ]; - const { - choices = [], - expiresAt = null, - multiple = false - } = note.poll || {}; - - const asPoll = note.poll ? { + const asPoll = poll ? { type: 'Question', content: toHtml(Object.assign({}, note, { text: text })), _misskey_fallback_content: content, - [expiresAt && expiresAt < new Date() ? 'closed' : 'endTime']: expiresAt, - [multiple ? 'anyOf' : 'oneOf']: choices.map(({ text, votes }) => ({ + [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, + [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ type: 'Note', name: text, replies: { type: 'Collection', - totalItems: votes + totalItems: poll.votes[i] } })) } : {}; return { - id: `${config.url}/notes/${note._id}`, + id: `${config.url}/notes/${note.id}`, type: 'Note', attributedTo, summary, @@ -172,17 +163,17 @@ export default async function renderNote(note: INote, dive = true): Promise cc, inReplyTo, attachment: files.map(renderDocument), - sensitive: files.some(file => file.metadata.isSensitive), + sensitive: files.some(file => file.isSensitive), tag, ...asPoll }; } -export async function getEmojis(names: string[]): Promise { - if (names == null || names.length < 1) return []; +export async function getEmojis(names: string[]): Promise { + if (names == null || names.length === 0) return []; const emojis = await Promise.all( - names.map(name => Emoji.findOne({ + names.map(name => Emojis.findOne({ name, host: null })) diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index 77e60cd61a..4c6b518eb6 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -1,21 +1,22 @@ import renderImage from './image'; import renderKey from './key'; import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; import { toHtml } from '../../../mfm/toHtml'; import { parse } from '../../../mfm/parse'; -import DriveFile from '../../../models/drive-file'; import { getEmojis } from './note'; import renderEmoji from './emoji'; import { IIdentifier } from '../models/identifier'; import renderHashtag from './hashtag'; +import { DriveFiles, UserServiceLinkings, UserKeypairs } from '../../../models'; -export default async (user: ILocalUser) => { - const id = `${config.url}/users/${user._id}`; +export async function renderPerson(user: ILocalUser) { + const id = `${config.url}/users/${user.id}`; - const [avatar, banner] = await Promise.all([ - DriveFile.findOne({ _id: user.avatarId }), - DriveFile.findOne({ _id: user.bannerId }) + const [avatar, banner, links] = await Promise.all([ + DriveFiles.findOne(user.avatarId), + DriveFiles.findOne(user.bannerId), + UserServiceLinkings.findOne({ userId: user.id }) ]); const attachment: { @@ -26,41 +27,41 @@ export default async (user: ILocalUser) => { identifier?: IIdentifier }[] = []; - if (user.twitter) { + if (links.twitter) { attachment.push({ type: 'PropertyValue', name: 'Twitter', - value: `@${user.twitter.screenName}`, + value: `@${links.twitterScreenName}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:twitter', - value: `${user.twitter.userId}@${user.twitter.screenName}` + value: `${links.twitterUserId}@${links.twitterScreenName}` } }); } - if (user.github) { + if (links.github) { attachment.push({ type: 'PropertyValue', name: 'GitHub', - value: `@${user.github.login}`, + value: `@${links.githubLogin}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:github', - value: `${user.github.id}@${user.github.login}` + value: `${links.githubId}@${links.githubLogin}` } }); } - if (user.discord) { + if (links.discord) { attachment.push({ type: 'PropertyValue', name: 'Discord', - value: `${user.discord.username}#${user.discord.discriminator}`, + value: `${links.discordUsername}#${links.discordDiscriminator}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:discord', - value: `${user.discord.id}@${user.discord.username}#${user.discord.discriminator}` + value: `${links.discordId}@${links.discordUsername}#${links.discordDiscriminator}` } }); } @@ -75,6 +76,10 @@ export default async (user: ILocalUser) => { ...hashtagTags, ]; + const keypair = await UserKeypairs.findOne({ + userId: user.id + }); + return { type: user.isBot ? 'Service' : 'Person', id, @@ -93,8 +98,8 @@ export default async (user: ILocalUser) => { image: user.bannerId && renderImage(banner), tag, manuallyApprovesFollowers: user.isLocked, - publicKey: renderKey(user), + publicKey: renderKey(user, keypair), isCat: user.isCat, attachment: attachment.length ? attachment : undefined }; -}; +} diff --git a/src/remote/activitypub/renderer/question.ts b/src/remote/activitypub/renderer/question.ts index cf0bf387c8..6ade10d1bf 100644 --- a/src/remote/activitypub/renderer/question.ts +++ b/src/remote/activitypub/renderer/question.ts @@ -1,19 +1,20 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; -import { INote } from '../../../models/note'; +import { ILocalUser } from '../../../models/entities/user'; +import { Note } from '../../../models/entities/note'; +import { Poll } from '../../../models/entities/poll'; -export default async function renderQuestion(user: ILocalUser, note: INote) { +export default async function renderQuestion(user: ILocalUser, note: Note, poll: Poll) { const question = { type: 'Question', - id: `${config.url}/questions/${note._id}`, - actor: `${config.url}/users/${user._id}`, + id: `${config.url}/questions/${note.id}`, + actor: `${config.url}/users/${user.id}`, content: note.text || '', - [note.poll.multiple ? 'anyOf' : 'oneOf']: note.poll.choices.map(c => ({ - name: c.text, - _misskey_votes: c.votes, + [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ + name: text, + _misskey_votes: poll.votes[i], replies: { type: 'Collection', - totalItems: c.votes + totalItems: poll.votes[i] } })) }; diff --git a/src/remote/activitypub/renderer/reject.ts b/src/remote/activitypub/renderer/reject.ts index 6d7d23708a..c4e0ba0d0a 100644 --- a/src/remote/activitypub/renderer/reject.ts +++ b/src/remote/activitypub/renderer/reject.ts @@ -1,8 +1,8 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default (object: any, user: ILocalUser) => ({ type: 'Reject', - actor: `${config.url}/users/${user._id}`, + actor: `${config.url}/users/${user.id}`, object }); diff --git a/src/remote/activitypub/renderer/remove.ts b/src/remote/activitypub/renderer/remove.ts index ed840be751..1b9a6b8c05 100644 --- a/src/remote/activitypub/renderer/remove.ts +++ b/src/remote/activitypub/renderer/remove.ts @@ -1,9 +1,9 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default (user: ILocalUser, target: any, object: any) => ({ type: 'Remove', - actor: `${config.url}/users/${user._id}`, + actor: `${config.url}/users/${user.id}`, target, object }); diff --git a/src/remote/activitypub/renderer/undo.ts b/src/remote/activitypub/renderer/undo.ts index dbcf5732be..2ff6b61b90 100644 --- a/src/remote/activitypub/renderer/undo.ts +++ b/src/remote/activitypub/renderer/undo.ts @@ -1,8 +1,8 @@ import config from '../../../config'; -import { ILocalUser, IUser } from '../../../models/user'; +import { ILocalUser, User } from '../../../models/entities/user'; -export default (object: any, user: ILocalUser | IUser) => ({ +export default (object: any, user: ILocalUser | User) => ({ type: 'Undo', - actor: `${config.url}/users/${user._id}`, + actor: `${config.url}/users/${user.id}`, object }); diff --git a/src/remote/activitypub/renderer/update.ts b/src/remote/activitypub/renderer/update.ts index cf9acc9acb..c1d5ba29b2 100644 --- a/src/remote/activitypub/renderer/update.ts +++ b/src/remote/activitypub/renderer/update.ts @@ -1,10 +1,10 @@ import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default (object: any, user: ILocalUser) => { const activity = { - id: `${config.url}/users/${user._id}#updates/${new Date().getTime()}`, - actor: `${config.url}/users/${user._id}`, + id: `${config.url}/users/${user.id}#updates/${new Date().getTime()}`, + actor: `${config.url}/users/${user.id}`, type: 'Update', to: [ 'https://www.w3.org/ns/activitystreams#Public' ], object diff --git a/src/remote/activitypub/renderer/vote.ts b/src/remote/activitypub/renderer/vote.ts index 014b76765b..8929c03460 100644 --- a/src/remote/activitypub/renderer/vote.ts +++ b/src/remote/activitypub/renderer/vote.ts @@ -1,22 +1,23 @@ import config from '../../../config'; -import { INote } from '../../../models/note'; -import { IRemoteUser, ILocalUser } from '../../../models/user'; -import { IPollVote } from '../../../models/poll-vote'; +import { Note } from '../../../models/entities/note'; +import { IRemoteUser, ILocalUser } from '../../../models/entities/user'; +import { PollVote } from '../../../models/entities/poll-vote'; +import { Poll } from '../../../models/entities/poll'; -export default async function renderVote(user: ILocalUser, vote: IPollVote, pollNote: INote, pollOwner: IRemoteUser): Promise { +export default async function renderVote(user: ILocalUser, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser): Promise { return { - id: `${config.url}/users/${user._id}#votes/${vote._id}/activity`, - actor: `${config.url}/users/${user._id}`, + id: `${config.url}/users/${user.id}#votes/${vote.id}/activity`, + actor: `${config.url}/users/${user.id}`, type: 'Create', to: [pollOwner.uri], published: new Date().toISOString(), object: { - id: `${config.url}/users/${user._id}#votes/${vote._id}`, + id: `${config.url}/users/${user.id}#votes/${vote.id}`, type: 'Note', - attributedTo: `${config.url}/users/${user._id}`, + attributedTo: `${config.url}/users/${user.id}`, to: [pollOwner.uri], - inReplyTo: pollNote.uri, - name: pollNote.poll.choices.find(x => x.id === vote.choice).text + inReplyTo: note.uri, + name: poll.choices[vote.choice] } }; } diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index 08dd7a6ba9..a089ed371c 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -7,10 +7,11 @@ import * as promiseAny from 'promise-any'; import { toUnicode } from 'punycode'; import config from '../../config'; -import { ILocalUser } from '../../models/user'; +import { ILocalUser } from '../../models/entities/user'; import { publishApLogStream } from '../../services/stream'; import { apLogger } from './logger'; -import Instance from '../../models/instance'; +import { UserKeypairs } from '../../models'; +import fetchMeta from '../../misc/fetch-meta'; export const logger = apLogger.createSubLogger('deliver'); @@ -23,8 +24,8 @@ export default async (user: ILocalUser, url: string, object: any) => { // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく - const instance = await Instance.findOne({ host: toUnicode(host) }); - if (instance && instance.isBlocked) return; + const meta = await fetchMeta(); + if (meta.blockedHosts.includes(toUnicode(host))) return; const data = JSON.stringify(object); @@ -35,6 +36,10 @@ export default async (user: ILocalUser, url: string, object: any) => { const addr = await resolveAddr(hostname); if (!addr) return; + const keypair = await UserKeypairs.findOne({ + userId: user.id + }); + const _ = new Promise((resolve, reject) => { const req = request({ protocol, @@ -62,8 +67,8 @@ export default async (user: ILocalUser, url: string, object: any) => { sign(req, { authorizationHeaderName: 'Signature', - key: user.keypair, - keyId: `${config.url}/users/${user._id}/publickey`, + key: keypair.keyPem, + keyId: `${config.url}/users/${user.id}/publickey`, headers: ['date', 'host', 'digest'] }); diff --git a/src/remote/activitypub/resolver.ts b/src/remote/activitypub/resolver.ts index 05152993e4..e8d0be638a 100644 --- a/src/remote/activitypub/resolver.ts +++ b/src/remote/activitypub/resolver.ts @@ -64,7 +64,7 @@ export default class Resolver { json: true }); - if (object === null || ( + if (object == null || ( Array.isArray(object['@context']) ? !object['@context'].includes('https://www.w3.org/ns/activitystreams') : object['@context'] !== 'https://www.w3.org/ns/activitystreams' diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index 400293da89..be846ab279 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -1,20 +1,21 @@ import { toUnicode, toASCII } from 'punycode'; -import User, { IUser, IRemoteUser } from '../models/user'; import webFinger from './webfinger'; import config from '../config'; import { createPerson, updatePerson } from './activitypub/models/person'; import { URL } from 'url'; import { remoteLogger } from './logger'; import chalk from 'chalk'; +import { User, IRemoteUser } from '../models/entities/user'; +import { Users } from '../models'; const logger = remoteLogger.createSubLogger('resolve-user'); -export default async (username: string, _host: string, option?: any, resync?: boolean): Promise => { +export default async (username: string, _host: string, option?: any, resync = false): Promise => { const usernameLower = username.toLowerCase(); if (_host == null) { logger.info(`return local user: ${usernameLower}`); - return await User.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }); } const configHostAscii = toASCII(config.host).toLowerCase(); @@ -25,14 +26,14 @@ export default async (username: string, _host: string, option?: any, resync?: bo if (configHost == host) { logger.info(`return local user: ${usernameLower}`); - return await User.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }); } - const user = await User.findOne({ usernameLower, host }, option); + const user = await Users.findOne({ usernameLower, host }, option); const acctLower = `${usernameLower}@${hostAscii}`; - if (user === null) { + if (user == null) { const self = await resolveSelf(acctLower); logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); @@ -54,13 +55,11 @@ export default async (username: string, _host: string, option?: any, resync?: bo throw new Error(`Invalied uri`); } - await User.update({ + await Users.update({ usernameLower, host: host }, { - $set: { - uri: self.href - } + uri: self.href }); } else { logger.info(`uri is fine: ${acctLower}`); @@ -69,7 +68,7 @@ export default async (username: string, _host: string, option?: any, resync?: bo await updatePerson(self.href); logger.info(`return resynced remote user: ${acctLower}`); - return await User.findOne({ uri: self.href }); + return await Users.findOne({ uri: self.href }); } logger.info(`return existing remote user: ${acctLower}`); -- cgit v1.2.3-freya