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/server/api/stream/channel.ts | 8 + src/server/api/stream/channels/admin.ts | 2 +- src/server/api/stream/channels/drive.ts | 2 +- .../api/stream/channels/games/reversi-game.ts | 168 ++++++++++----------- src/server/api/stream/channels/games/reversi.ts | 13 +- src/server/api/stream/channels/global-timeline.ts | 18 +-- src/server/api/stream/channels/hashtag.ts | 54 ++++--- src/server/api/stream/channels/home-timeline.ts | 45 +++--- src/server/api/stream/channels/hybrid-timeline.ts | 55 ------- src/server/api/stream/channels/index.ts | 4 +- src/server/api/stream/channels/local-timeline.ts | 45 +++--- src/server/api/stream/channels/main.ts | 9 +- src/server/api/stream/channels/messaging-index.ts | 2 +- src/server/api/stream/channels/messaging.ts | 4 +- src/server/api/stream/channels/social-timeline.ts | 64 ++++++++ src/server/api/stream/channels/user-list.ts | 72 ++++++++- src/server/api/stream/index.ts | 64 ++++++-- 17 files changed, 371 insertions(+), 258 deletions(-) delete mode 100644 src/server/api/stream/channels/hybrid-timeline.ts create mode 100644 src/server/api/stream/channels/social-timeline.ts (limited to 'src/server/api/stream') diff --git a/src/server/api/stream/channel.ts b/src/server/api/stream/channel.ts index bdbe4605cf..18fa651820 100644 --- a/src/server/api/stream/channel.ts +++ b/src/server/api/stream/channel.ts @@ -15,6 +15,14 @@ export default abstract class Channel { return this.connection.user; } + protected get following() { + return this.connection.following; + } + + protected get muting() { + return this.connection.muting; + } + protected get subscriber() { return this.connection.subscriber; } diff --git a/src/server/api/stream/channels/admin.ts b/src/server/api/stream/channels/admin.ts index 6bcd1a7e0b..e2eba10f78 100644 --- a/src/server/api/stream/channels/admin.ts +++ b/src/server/api/stream/channels/admin.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe admin stream - this.subscriber.on(`adminStream:${this.user._id}`, data => { + this.subscriber.on(`adminStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/drive.ts b/src/server/api/stream/channels/drive.ts index 391c4b5c32..671aad4366 100644 --- a/src/server/api/stream/channels/drive.ts +++ b/src/server/api/stream/channels/drive.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe drive stream - this.subscriber.on(`driveStream:${this.user._id}`, data => { + this.subscriber.on(`driveStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index 87df9e194c..158f108c4e 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -1,22 +1,22 @@ import autobind from 'autobind-decorator'; import * as CRC32 from 'crc-32'; -import * as mongo from 'mongodb'; -import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; import { publishReversiGameStream } from '../../../../../services/stream'; import Reversi from '../../../../../games/reversi/core'; import * as maps from '../../../../../games/reversi/maps'; import Channel from '../../channel'; +import { ReversiGame } from '../../../../../models/entities/games/reversi/game'; +import { ReversiGames } from '../../../../../models'; export default class extends Channel { public readonly chName = 'gamesReversiGame'; public static shouldShare = false; public static requireCredential = false; - private gameId: mongo.ObjectID; + private gameId: ReversiGame['id']; @autobind public async init(params: any) { - this.gameId = new mongo.ObjectID(params.gameId as string); + this.gameId = params.gameId; // Subscribe game stream this.subscriber.on(`reversiGameStream:${this.gameId}`, data => { @@ -29,7 +29,7 @@ export default class extends Channel { switch (type) { case 'accept': this.accept(true); break; case 'cancelAccept': this.accept(false); break; - case 'updateSettings': this.updateSettings(body.settings); break; + case 'updateSettings': this.updateSettings(body.key, body.value); break; case 'initForm': this.initForm(body); break; case 'updateForm': this.updateForm(body.id, body.value); break; case 'message': this.message(body); break; @@ -39,54 +39,55 @@ export default class extends Channel { } @autobind - private async updateSettings(settings: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + private async updateSettings(key: string, value: any) { + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; - if (game.user1Id.equals(this.user._id) && game.user1Accepted) return; - if (game.user2Id.equals(this.user._id) && game.user2Accepted) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; + if ((game.user1Id === this.user.id) && game.user1Accepted) return; + if ((game.user2Id === this.user.id) && game.user2Accepted) return; - await ReversiGame.update({ _id: this.gameId }, { - $set: { - settings - } + if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; + + await ReversiGames.update({ id: this.gameId }, { + [key]: value }); - publishReversiGameStream(this.gameId, 'updateSettings', settings); + publishReversiGameStream(this.gameId, 'updateSettings', { + key: key, + value: value + }); } @autobind private async initForm(form: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const set = game.user1Id.equals(this.user._id) ? { + const set = game.user1Id === this.user.id ? { form1: form } : { - form2: form - }; + form2: form + }; - await ReversiGame.update({ _id: this.gameId }, { - $set: set - }); + await ReversiGames.update({ id: this.gameId }, set); publishReversiGameStream(this.gameId, 'initForm', { - userId: this.user._id, + userId: this.user.id, form }); } @autobind private async updateForm(id: string, value: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne({ id: this.gameId }); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const form = game.user1Id.equals(this.user._id) ? game.form2 : game.form1; + const form = game.user1Id === this.user.id ? game.form2 : game.form1; const item = form.find((i: any) => i.id == id); @@ -94,18 +95,16 @@ export default class extends Channel { item.value = value; - const set = game.user1Id.equals(this.user._id) ? { + const set = game.user1Id === this.user.id ? { form2: form } : { form1: form }; - await ReversiGame.update({ _id: this.gameId }, { - $set: set - }); + await ReversiGames.update({ id: this.gameId }, set); publishReversiGameStream(this.gameId, 'updateForm', { - userId: this.user._id, + userId: this.user.id, id, value }); @@ -115,24 +114,22 @@ export default class extends Channel { private async message(message: any) { message.id = Math.random(); publishReversiGameStream(this.gameId, 'message', { - userId: this.user._id, + userId: this.user.id, message }); } @autobind private async accept(accept: boolean) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; let bothAccepted = false; - if (game.user1Id.equals(this.user._id)) { - await ReversiGame.update({ _id: this.gameId }, { - $set: { - user1Accepted: accept - } + if (game.user1Id === this.user.id) { + await ReversiGames.update({ id: this.gameId }, { + user1Accepted: accept }); publishReversiGameStream(this.gameId, 'changeAccepts', { @@ -141,11 +138,9 @@ export default class extends Channel { }); if (accept && game.user2Accepted) bothAccepted = true; - } else if (game.user2Id.equals(this.user._id)) { - await ReversiGame.update({ _id: this.gameId }, { - $set: { - user2Accepted: accept - } + } else if (game.user2Id === this.user.id) { + await ReversiGames.update({ id: this.gameId }, { + user2Accepted: accept }); publishReversiGameStream(this.gameId, 'changeAccepts', { @@ -161,15 +156,15 @@ export default class extends Channel { if (bothAccepted) { // 3秒後、まだacceptされていたらゲーム開始 setTimeout(async () => { - const freshGame = await ReversiGame.findOne({ _id: this.gameId }); + const freshGame = await ReversiGames.findOne(this.gameId); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; let bw: number; - if (freshGame.settings.bw == 'random') { + if (freshGame.bw == 'random') { bw = Math.random() > 0.5 ? 1 : 2; } else { - bw = freshGame.settings.bw as number; + bw = parseInt(freshGame.bw, 10); } function getRandomMap() { @@ -178,22 +173,20 @@ export default class extends Channel { return Object.values(maps)[rnd].data; } - const map = freshGame.settings.map != null ? freshGame.settings.map : getRandomMap(); + const map = freshGame.map != null ? freshGame.map : getRandomMap(); - await ReversiGame.update({ _id: this.gameId }, { - $set: { - startedAt: new Date(), - isStarted: true, - black: bw, - 'settings.map': map - } + await ReversiGames.update({ id: this.gameId }, { + startedAt: new Date(), + isStarted: true, + black: bw, + map: map }); //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 const o = new Reversi(map, { - isLlotheo: freshGame.settings.isLlotheo, - canPutEverywhere: freshGame.settings.canPutEverywhere, - loopedBoard: freshGame.settings.loopedBoard + isLlotheo: freshGame.isLlotheo, + canPutEverywhere: freshGame.canPutEverywhere, + loopedBoard: freshGame.loopedBoard }); if (o.isEnded) { @@ -206,23 +199,22 @@ export default class extends Channel { winner = null; } - await ReversiGame.update({ - _id: this.gameId + await ReversiGames.update({ + id: this.gameId }, { - $set: { - isEnded: true, - winnerId: winner - } - }); + isEnded: true, + winnerId: winner + }); publishReversiGameStream(this.gameId, 'ended', { winnerId: winner, - game: await pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId, this.user) }); } //#endregion - publishReversiGameStream(this.gameId, 'started', await pack(this.gameId, this.user)); + publishReversiGameStream(this.gameId, 'started', + await ReversiGames.pack(this.gameId, this.user)); }, 3000); } } @@ -230,16 +222,16 @@ export default class extends Channel { // 石を打つ @autobind private async set(pos: number) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (!game.isStarted) return; if (game.isEnded) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const o = new Reversi(game.settings.map, { - isLlotheo: game.settings.isLlotheo, - canPutEverywhere: game.settings.canPutEverywhere, - loopedBoard: game.settings.loopedBoard + const o = new Reversi(game.map, { + isLlotheo: game.isLlotheo, + canPutEverywhere: game.canPutEverywhere, + loopedBoard: game.loopedBoard }); for (const log of game.logs) { @@ -247,7 +239,7 @@ export default class extends Channel { } const myColor = - (game.user1Id.equals(this.user._id) && game.black == 1) || (game.user2Id.equals(this.user._id) && game.black == 2) + ((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2) ? true : false; @@ -271,20 +263,18 @@ export default class extends Channel { pos }; - const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()); + const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString(); - await ReversiGame.update({ - _id: this.gameId + game.logs.push(log); + + await ReversiGames.update({ + id: this.gameId }, { - $set: { - crc32, - isEnded: o.isEnded, - winnerId: winner - }, - $push: { - logs: log - } - }); + crc32, + isEnded: o.isEnded, + winnerId: winner, + logs: game.logs + }); publishReversiGameStream(this.gameId, 'set', Object.assign(log, { next: o.turn @@ -293,14 +283,14 @@ export default class extends Channel { if (o.isEnded) { publishReversiGameStream(this.gameId, 'ended', { winnerId: winner, - game: await pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId, this.user) }); } } @autobind private async check(crc32: string) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (!game.isStarted) return; @@ -308,7 +298,7 @@ export default class extends Channel { if (game.crc32 == null) return; if (crc32 !== game.crc32) { - this.send('rescue', await pack(game, this.user)); + this.send('rescue', await ReversiGames.pack(game, this.user)); } } } diff --git a/src/server/api/stream/channels/games/reversi.ts b/src/server/api/stream/channels/games/reversi.ts index 1b1ad187a3..0498e5e017 100644 --- a/src/server/api/stream/channels/games/reversi.ts +++ b/src/server/api/stream/channels/games/reversi.ts @@ -1,8 +1,7 @@ import autobind from 'autobind-decorator'; -import * as mongo from 'mongodb'; -import Matching, { pack } from '../../../../../models/games/reversi/matching'; import { publishMainStream } from '../../../../../services/stream'; import Channel from '../../channel'; +import { ReversiMatchings } from '../../../../../models'; export default class extends Channel { public readonly chName = 'gamesReversi'; @@ -12,7 +11,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user._id}`, data => { + this.subscriber.on(`reversiStream:${this.user.id}`, data => { this.send(data); }); } @@ -22,12 +21,12 @@ export default class extends Channel { switch (type) { case 'ping': if (body.id == null) return; - const matching = await Matching.findOne({ - parentId: this.user._id, - childId: new mongo.ObjectID(body.id) + const matching = await ReversiMatchings.findOne({ + parentId: this.user.id, + childId: body.id }); if (matching == null) return; - publishMainStream(matching.childId, 'reversiInvited', await pack(matching, matching.childId)); + publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, matching.childId)); break; } } diff --git a/src/server/api/stream/channels/global-timeline.ts b/src/server/api/stream/channels/global-timeline.ts index b3689d47f5..bfb7697ba7 100644 --- a/src/server/api/stream/channels/global-timeline.ts +++ b/src/server/api/stream/channels/global-timeline.ts @@ -1,17 +1,14 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'globalTimeline'; public static shouldShare = true; public static requireCredential = false; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { const meta = await fetchMeta(); @@ -20,29 +17,26 @@ export default class extends Channel { } // Subscribe events - this.subscriber.on('globalTimeline', this.onNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { // リプライなら再pack if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { + note.reply = await Notes.pack(note.replyId, this.user, { detail: true }); } // Renoteなら再pack if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + note.renote = await Notes.pack(note.renoteId, this.user, { detail: true }); } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -50,6 +44,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off('globalTimeline', this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/hashtag.ts b/src/server/api/stream/channels/hashtag.ts index 586ce02f06..36c56c7ab6 100644 --- a/src/server/api/stream/channels/hashtag.ts +++ b/src/server/api/stream/channels/hashtag.ts @@ -1,40 +1,46 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'hashtag'; public static shouldShare = false; public static requireCredential = false; + private q: string[][]; @autobind public async init(params: any) { - const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null; - const mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : []; + this.q = params.q; - const q: string[][] = params.q; - - if (q == null) return; + if (this.q == null) return; // Subscribe stream - this.subscriber.on('hashtag', async note => { - const noteTags = note.tags.map((t: string) => t.toLowerCase()); - const matched = q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase()))); - if (!matched) return; - - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { - detail: true - }); - } - - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, mutedUserIds)) return; - - this.send('note', note); - }); + this.subscriber.on('notesStream', this.onNote); + } + + @autobind + private async onNote(note: any) { + const noteTags = note.tags.map((t: string) => t.toLowerCase()); + const matched = this.q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase()))); + if (!matched) return; + + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts index 3c0b238720..2cece0947f 100644 --- a/src/server/api/stream/channels/home-timeline.ts +++ b/src/server/api/stream/channels/home-timeline.ts @@ -1,42 +1,49 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'homeTimeline'; public static shouldShare = true; public static requireCredential = true; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { // Subscribe events - this.subscriber.on(`homeTimeline:${this.user._id}`, this.onNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + // その投稿のユーザーをフォローしていなかったら弾く + if (this.user.id !== note.userId && !this.following.includes(note.userId)) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -44,6 +51,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off(`homeTimeline:${this.user._id}`, this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts deleted file mode 100644 index 35ef17b56b..0000000000 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ /dev/null @@ -1,55 +0,0 @@ -import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; -import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; -import Channel from '../channel'; -import fetchMeta from '../../../../misc/fetch-meta'; - -export default class extends Channel { - public readonly chName = 'hybridTimeline'; - public static shouldShare = true; - public static requireCredential = true; - - private mutedUserIds: string[] = []; - - @autobind - public async init(params: any) { - const meta = await fetchMeta(); - if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; - - // Subscribe events - this.subscriber.on('hybridTimeline', this.onNewNote); - this.subscriber.on(`hybridTimeline:${this.user._id}`, this.onNewNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); - } - - @autobind - private async onNewNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { - detail: true - }); - } - - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; - - this.send('note', note); - } - - @autobind - public dispose() { - // Unsubscribe events - this.subscriber.off('hybridTimeline', this.onNewNote); - this.subscriber.off(`hybridTimeline:${this.user._id}`, this.onNewNote); - } -} diff --git a/src/server/api/stream/channels/index.ts b/src/server/api/stream/channels/index.ts index 4527fb1e46..199ab0a809 100644 --- a/src/server/api/stream/channels/index.ts +++ b/src/server/api/stream/channels/index.ts @@ -1,7 +1,7 @@ import main from './main'; import homeTimeline from './home-timeline'; import localTimeline from './local-timeline'; -import hybridTimeline from './hybrid-timeline'; +import socialTimeline from './social-timeline'; import globalTimeline from './global-timeline'; import notesStats from './notes-stats'; import serverStats from './server-stats'; @@ -20,7 +20,7 @@ export default { main, homeTimeline, localTimeline, - hybridTimeline, + socialTimeline, globalTimeline, notesStats, serverStats, diff --git a/src/server/api/stream/channels/local-timeline.ts b/src/server/api/stream/channels/local-timeline.ts index 3402023192..4aec2d66b4 100644 --- a/src/server/api/stream/channels/local-timeline.ts +++ b/src/server/api/stream/channels/local-timeline.ts @@ -1,17 +1,14 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'localTimeline'; public static shouldShare = true; public static requireCredential = false; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { const meta = await fetchMeta(); @@ -20,29 +17,39 @@ export default class extends Channel { } // Subscribe events - this.subscriber.on('localTimeline', this.onNote); - - const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null; - this.mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : []; + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + if (note.user.host !== null) return; + if (note.visibility === 'home') return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -50,6 +57,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off('localTimeline', this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts index 175d914fa5..0d9bf3149d 100644 --- a/src/server/api/stream/channels/main.ts +++ b/src/server/api/stream/channels/main.ts @@ -1,6 +1,6 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; import Channel from '../channel'; +import { Mutings } from '../../../../models'; export default class extends Channel { public readonly chName = 'main'; @@ -9,16 +9,15 @@ export default class extends Channel { @autobind public async init(params: any) { - const mute = await Mute.find({ muterId: this.user._id }); - const mutedUserIds = mute.map(m => m.muteeId.toString()); + const mute = await Mutings.find({ muterId: this.user.id }); // Subscribe main stream channel - this.subscriber.on(`mainStream:${this.user._id}`, async data => { + this.subscriber.on(`mainStream:${this.user.id}`, async data => { const { type, body } = data; switch (type) { case 'notification': { - if (mutedUserIds.includes(body.userId)) return; + if (mute.map(m => m.muteeId).includes(body.userId)) return; if (body.note && body.note.isHidden) return; break; } diff --git a/src/server/api/stream/channels/messaging-index.ts b/src/server/api/stream/channels/messaging-index.ts index 148ff7f935..648badc1dc 100644 --- a/src/server/api/stream/channels/messaging-index.ts +++ b/src/server/api/stream/channels/messaging-index.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe messaging index stream - this.subscriber.on(`messagingIndexStream:${this.user._id}`, data => { + this.subscriber.on(`messagingIndexStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index 0d81b4e45c..b81fbb9d4c 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -14,7 +14,7 @@ export default class extends Channel { this.otherpartyId = params.otherparty as string; // Subscribe messaging stream - this.subscriber.on(`messagingStream:${this.user._id}-${this.otherpartyId}`, data => { + this.subscriber.on(`messagingStream:${this.user.id}-${this.otherpartyId}`, data => { this.send(data); }); } @@ -23,7 +23,7 @@ export default class extends Channel { public onMessage(type: string, body: any) { switch (type) { case 'read': - read(this.user._id, this.otherpartyId, body.id); + read(this.user.id, this.otherpartyId, body.id); break; } } diff --git a/src/server/api/stream/channels/social-timeline.ts b/src/server/api/stream/channels/social-timeline.ts new file mode 100644 index 0000000000..1d76eed297 --- /dev/null +++ b/src/server/api/stream/channels/social-timeline.ts @@ -0,0 +1,64 @@ +import autobind from 'autobind-decorator'; +import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; +import Channel from '../channel'; +import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; + +export default class extends Channel { + public readonly chName = 'socialTimeline'; + public static shouldShare = true; + public static requireCredential = true; + + @autobind + public async init(params: any) { + const meta = await fetchMeta(); + if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; + + // Subscribe events + this.subscriber.on('notesStream', this.onNote); + } + + @autobind + private async onNote(note: any) { + // 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ + if (!( + this.user.id === note.userId || + this.following.includes(note.userId) || + note.user.host === null + )) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { + detail: true + }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off('notesStream', this.onNote); + } +} diff --git a/src/server/api/stream/channels/user-list.ts b/src/server/api/stream/channels/user-list.ts index 5debf41770..f5434b8f08 100644 --- a/src/server/api/stream/channels/user-list.ts +++ b/src/server/api/stream/channels/user-list.ts @@ -1,23 +1,81 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; -import { pack } from '../../../../models/note'; +import { Notes, UserListJoinings } from '../../../../models'; +import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; +import { User } from '../../../../models/entities/user'; export default class extends Channel { public readonly chName = 'userList'; public static shouldShare = false; public static requireCredential = false; + private listId: string; + public listUsers: User['id'][] = []; + private listUsersClock: NodeJS.Timer; @autobind public async init(params: any) { - const listId = params.listId as string; + this.listId = params.listId as string; // Subscribe stream - this.subscriber.on(`userListStream:${listId}`, async data => { - // 再パック - if (data.type == 'note') data.body = await pack(data.body.id, this.user, { + this.subscriber.on(`userListStream:${this.listId}`, this.send); + + this.subscriber.on('notesStream', this.onNote); + + this.updateListUsers(); + this.listUsersClock = setInterval(this.updateListUsers, 5000); + } + + @autobind + private async updateListUsers() { + const users = await UserListJoinings.find({ + where: { + userListId: this.listId, + }, + select: ['userId'] + }); + + this.listUsers = users.map(x => x.userId); + } + + @autobind + private async onNote(note: any) { + if (!this.listUsers.includes(note.userId)) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); - this.send(data); - }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off(`userListStream:${this.listId}`, this.send); + this.subscriber.off('notesStream', this.onNote); + + clearInterval(this.listUsersClock); } } diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index 22f7646cb9..abbd91ec81 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -1,33 +1,35 @@ import autobind from 'autobind-decorator'; import * as websocket from 'websocket'; - -import User, { IUser } from '../../../models/user'; -import readNotification from '../common/read-notification'; +import { readNotification } from '../common/read-notification'; import call from '../call'; -import { IApp } from '../../../models/app'; import readNote from '../../../services/note/read'; - import Channel from './channel'; import channels from './channels'; import { EventEmitter } from 'events'; +import { User } from '../../../models/entities/user'; +import { App } from '../../../models/entities/app'; +import { Users, Followings, Mutings } from '../../../models'; /** * Main stream connection */ export default class Connection { - public user?: IUser; - public app: IApp; + public user?: User; + public following: User['id'][] = []; + public muting: User['id'][] = []; + public app: App; private wsConnection: websocket.connection; public subscriber: EventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; - public sendMessageToWsOverride: any = null; // 後方互換性のため + private followingClock: NodeJS.Timer; + private mutingClock: NodeJS.Timer; constructor( wsConnection: websocket.connection, subscriber: EventEmitter, - user: IUser, - app: IApp + user: User, + app: App ) { this.wsConnection = wsConnection; this.user = user; @@ -35,6 +37,14 @@ export default class Connection { this.subscriber = subscriber; this.wsConnection.on('message', this.onWsConnectionMessage); + + if (this.user) { + this.updateFollowing(); + this.followingClock = setInterval(this.updateFollowing, 5000); + + this.updateMuting(); + this.mutingClock = setInterval(this.updateMuting, 5000); + } } /** @@ -64,7 +74,7 @@ export default class Connection { @autobind private async onApiRequest(payload: any) { // 新鮮なデータを利用するためにユーザーをフェッチ - const user = this.user ? await User.findOne({ _id: this.user._id }) : null; + const user = this.user ? await Users.findOne(this.user.id) : null; const endpoint = payload.endpoint || payload.ep; // alias @@ -79,7 +89,7 @@ export default class Connection { @autobind private onReadNotification(payload: any) { if (!payload.id) return; - readNotification(this.user._id, payload.id); + readNotification(this.user.id, [payload.id]); } /** @@ -100,7 +110,7 @@ export default class Connection { } if (payload.read) { - readNote(this.user._id, payload.id); + readNote(this.user.id, payload.id); } } @@ -150,7 +160,6 @@ export default class Connection { */ @autobind public sendMessageToWs(type: string, payload: any) { - if (this.sendMessageToWsOverride) return this.sendMessageToWsOverride(type, payload); // 後方互換性のため this.wsConnection.send(JSON.stringify({ type: type, body: payload @@ -208,6 +217,30 @@ export default class Connection { } } + @autobind + private async updateFollowing() { + const followings = await Followings.find({ + where: { + followerId: this.user.id + }, + select: ['followeeId'] + }); + + this.following = followings.map(x => x.followeeId); + } + + @autobind + private async updateMuting() { + const mutings = await Mutings.find({ + where: { + muterId: this.user.id + }, + select: ['muteeId'] + }); + + this.muting = mutings.map(x => x.muteeId); + } + /** * ストリームが切れたとき */ @@ -216,5 +249,8 @@ export default class Connection { for (const c of this.channels.filter(c => c.dispose)) { c.dispose(); } + + if (this.followingClock) clearInterval(this.followingClock); + if (this.mutingClock) clearInterval(this.mutingClock); } } -- cgit v1.2.3-freya