Use PostgreSQL instead of MongoDB (#4572)

* 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

* ✌️

* wip

* Update note.ts

* Update postgre.ts

* wip

* wip

* wip

* wip

* Update add-file.ts

* wip

* wip

* wip

* Clean up

* Update logs.ts

* wip

* 🍕

* 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

* ✌️

* clean up

* docs

* Update push.ts

* wip

* Update api.ts

* wip

* ✌️

* Update make-pagination-query.ts

* ✌️

* 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

* 🎨

* 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

* ✌️

* 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

* 🎨

* 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

* ✌️

* Refactoring

#1540

* test

* test

* test

* test

* test

* test

* test

* Fix bug

* Fix test

* 🍣

* wip

* #4471

* Add test for #4335

* Refactor

* Fix test

* Add tests

* 🕓

* Fix bug

* Add test

* Add test

* rename

* Fix bug
This commit is contained in:
syuilo 2019-04-07 21:50:36 +09:00 committed by GitHub
parent 13caf37991
commit f0a29721c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
592 changed files with 13463 additions and 14147 deletions

View file

@ -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<IDriveFile> {
export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
// 投稿者が凍結されていたらスキップ
if (actor.isSuspended) {
return null;
@ -39,18 +40,16 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
throw e;
}
if (file.metadata.isRemote) {
if (file.isRemote) {
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
// URLを更新する
if (file.metadata.url !== image.url) {
file = await DriveFile.findOneAndUpdate({ _id: file._id }, {
$set: {
'metadata.url': image.url,
'metadata.uri': image.url
}
}, {
returnNewDocument: true
if (file.url !== image.url) {
await DriveFiles.update({ id: file.id }, {
url: image.url,
uri: image.url
});
file = DriveFiles.findOne(file.id);
}
}
@ -63,7 +62,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
* Misskeyに対象のImageが登録されていればそれを返し
* Misskeyに登録しそれを返します
*/
export async function resolveImage(actor: IRemoteUser, value: any): Promise<IDriveFile> {
export async function resolveImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
// TODO
// リモートサーバーからフェッチしてきて登録

View file

@ -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<INote> {
export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<Note> {
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<INote> {
export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<Note> {
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<IDriveFile>)))
? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x)) as Promise<DriveFile>)))
.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<null> => {
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<INote> {
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note> {
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<IUser>)
uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise<User>)
);
return users.filter(x => x != null);

View file

@ -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<IUser> {
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User> {
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<IUs
/**
* Personを作成します
*/
export async function createPerson(uri: string, resolver?: Resolver): Promise<IUser> {
export async function createPerson(uri: string, resolver?: Resolver): Promise<User> {
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<IU
logger.info(`Creating 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 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<IU
// Create user
let user: IRemoteUser;
try {
user = await User.insert({
user = await Users.save({
id: genId(),
avatarId: null,
bannerId: null,
createdAt: Date.parse(person.published) || null,
createdAt: Date.parse(person.published) || new Date(),
lastFetchedAt: new Date(),
description: fromHtml(person.summary),
followersCount,
followingCount,
notesCount,
name: person.name,
isLocked: person.manuallyApprovesFollowers,
username: person.preferredUsername,
usernameLower: person.preferredUsername.toLowerCase(),
host,
publicKey: {
id: person.publicKey.id,
publicKeyPem: person.publicKey.publicKeyPem
},
inbox: person.inbox,
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
featured: person.featured,
@ -179,10 +158,22 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
tags,
isBot,
isCat: (person as any).isCat === true
}) as IRemoteUser;
} as Partial<User>) 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<IU
throw e;
}
await UserServiceLinkings.save({
id: genId(),
userId: user.id
} as UserServiceLinking);
// Register host
registerOrFetchInstanceDoc(host).then(i => {
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<IDriveFile>([
const [avatar, banner] = (await Promise.all<DriveFile>([
person.icon,
person.image
].map(img =>
@ -225,22 +208,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
: resolveImage(user, img).catch(() => 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;
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 User.update({ _id: user._id }, {
$set: {
avatarId,
bannerId,
avatarUrl,
bannerUrl,
avatarColor,
bannerColor
}
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<IU
//#region カスタム絵文字取得
const emojis = await extractEmojis(person.tag, host).catch(e => {
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<IDriveFile>([
const [avatar, banner] = (await Promise.all<DriveFile>([
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<User>;
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
await Followings.update({
followerId: exist.id
}, {
$set: {
'_follower.sharedInbox': person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined)
}
}, {
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<IUser> {
export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise<User> {
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<INote>));
.map(item => limit(() => resolveNote(item, resolver)) as Promise<Note>));
await User.update({ _id: user._id }, {
$set: {
pinnedNoteIds: featuredNotes.filter(note => note != null).map(note => note._id)
}
});
for (const note of featuredNotes.filter(note => note != null)) {
UserNotePinings.save({
id: genId(),
createdAt: new Date(),
userId: user.id,
noteId: note.id
} as UserNotePining);
}
}

View file

@ -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<IPoll> {
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;