diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2018-06-18 14:43:56 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-06-18 14:43:56 +0900 |
| commit | 5d3943ffa8cb4090c5c111397e266d255cc2212b (patch) | |
| tree | 46f692fb21005bdc89499ea012c2436e8a2e90c0 /src/server/api | |
| parent | New translations ja.yml (Spanish) (diff) | |
| parent | yatta (diff) | |
| download | misskey-5d3943ffa8cb4090c5c111397e266d255cc2212b.tar.gz misskey-5d3943ffa8cb4090c5c111397e266d255cc2212b.tar.bz2 misskey-5d3943ffa8cb4090c5c111397e266d255cc2212b.zip | |
Merge branch 'master' into l10n_master
Diffstat (limited to 'src/server/api')
126 files changed, 1044 insertions, 1688 deletions
diff --git a/src/server/api/bot/core.ts b/src/server/api/bot/core.ts deleted file mode 100644 index d41af48057..0000000000 --- a/src/server/api/bot/core.ts +++ /dev/null @@ -1,439 +0,0 @@ -import * as EventEmitter from 'events'; -import * as bcrypt from 'bcryptjs'; - -import User, { IUser, init as initUser, ILocalUser } from '../../../models/user'; - -import getNoteSummary from '../../../renderers/get-note-summary'; -import getUserName from '../../../renderers/get-user-name'; -import getUserSummary from '../../../renderers/get-user-summary'; -import parseAcct from '../../../acct/parse'; -import getNotificationSummary from '../../../renderers/get-notification-summary'; - -const hmm = [ - '?', - 'ふぅ~む...?', - 'ちょっと何言ってるかわからないです', - '「ヘルプ」と言うと利用可能な操作が確認できますよ' -]; - -/** - * Botの頭脳 - */ -export default class BotCore extends EventEmitter { - public user: IUser = null; - - private context: Context = null; - - constructor(user?: IUser) { - super(); - - this.user = user; - } - - public clearContext() { - this.setContext(null); - } - - public setContext(context: Context) { - this.context = context; - this.emit('updated'); - - if (context) { - context.on('updated', () => { - this.emit('updated'); - }); - } - } - - public export() { - return { - user: this.user, - context: this.context ? this.context.export() : null - }; - } - - protected _import(data) { - this.user = data.user ? initUser(data.user) : null; - this.setContext(data.context ? Context.import(this, data.context) : null); - } - - public static import(data) { - const bot = new BotCore(); - bot._import(data); - return bot; - } - - public async q(query: string): Promise<string> { - if (this.context != null) { - return await this.context.q(query); - } - - if (/^@[a-zA-Z0-9_]+$/.test(query)) { - return await this.showUserCommand(query); - } - - switch (query) { - case 'ping': - return 'PONG'; - - case 'help': - case 'ヘルプ': - return '利用可能なコマンド一覧です:\n' + - 'help: これです\n' + - 'me: アカウント情報を見ます\n' + - 'login, signin: サインインします\n' + - 'logout, signout: サインアウトします\n' + - 'note: 投稿します\n' + - 'tl: タイムラインを見ます\n' + - 'no: 通知を見ます\n' + - '@<ユーザー名>: ユーザーを表示します\n' + - '\n' + - 'タイムラインや通知を見た後、「次」というとさらに遡ることができます。'; - - case 'me': - return this.user ? `${getUserName(this.user)}としてサインインしています。\n\n${getUserSummary(this.user)}` : 'サインインしていません'; - - case 'login': - case 'signin': - case 'ログイン': - case 'サインイン': - if (this.user != null) return '既にサインインしていますよ!'; - this.setContext(new SigninContext(this)); - return await this.context.greet(); - - case 'logout': - case 'signout': - case 'ログアウト': - case 'サインアウト': - if (this.user == null) return '今はサインインしてないですよ!'; - this.signout(); - return 'ご利用ありがとうございました <3'; - - case 'note': - case '投稿': - if (this.user == null) return 'まずサインインしてください。'; - this.setContext(new NoteContext(this)); - return await this.context.greet(); - - case 'tl': - case 'タイムライン': - if (this.user == null) return 'まずサインインしてください。'; - this.setContext(new TlContext(this)); - return await this.context.greet(); - - case 'no': - case 'notifications': - case '通知': - if (this.user == null) return 'まずサインインしてください。'; - this.setContext(new NotificationsContext(this)); - return await this.context.greet(); - - case 'guessing-game': - case '数当てゲーム': - this.setContext(new GuessingGameContext(this)); - return await this.context.greet(); - - default: - return hmm[Math.floor(Math.random() * hmm.length)]; - } - } - - public signin(user: IUser) { - this.user = user; - this.emit('signin', user); - this.emit('updated'); - } - - public signout() { - const user = this.user; - this.user = null; - this.emit('signout', user); - this.emit('updated'); - } - - public async refreshUser() { - this.user = await User.findOne({ - _id: this.user._id - }, { - fields: { - data: false - } - }); - - this.emit('updated'); - } - - public async showUserCommand(q: string): Promise<string> { - try { - const user = await require('../endpoints/users/show')(parseAcct(q.substr(1)), this.user); - - const text = getUserSummary(user); - - return text; - } catch (e) { - return `問題が発生したようです...: ${e}`; - } - } -} - -abstract class Context extends EventEmitter { - protected bot: BotCore; - - public abstract async greet(): Promise<string>; - public abstract async q(query: string): Promise<string>; - public abstract export(): any; - - constructor(bot: BotCore) { - super(); - this.bot = bot; - } - - public static import(bot: BotCore, data: any) { - if (data.type == 'guessing-game') return GuessingGameContext.import(bot, data.content); - if (data.type == 'note') return NoteContext.import(bot, data.content); - if (data.type == 'tl') return TlContext.import(bot, data.content); - if (data.type == 'notifications') return NotificationsContext.import(bot, data.content); - if (data.type == 'signin') return SigninContext.import(bot, data.content); - return null; - } -} - -class SigninContext extends Context { - private temporaryUser: ILocalUser = null; - - public async greet(): Promise<string> { - return 'まずユーザー名を教えてください:'; - } - - public async q(query: string): Promise<string> { - if (this.temporaryUser == null) { - // Fetch user - const user = await User.findOne({ - usernameLower: query.toLowerCase(), - host: null - }, { - fields: { - data: false - } - }) as ILocalUser; - - if (user === null) { - return `${query}というユーザーは存在しませんでした... もう一度教えてください:`; - } else { - this.temporaryUser = user; - this.emit('updated'); - return `パスワードを教えてください:`; - } - } else { - // Compare password - const same = await bcrypt.compare(query, this.temporaryUser.password); - - if (same) { - this.bot.signin(this.temporaryUser); - this.bot.clearContext(); - return `${getUserName(this.temporaryUser)}さん、おかえりなさい!`; - } else { - return `パスワードが違います... もう一度教えてください:`; - } - } - } - - public export() { - return { - type: 'signin', - content: { - temporaryUser: this.temporaryUser - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new SigninContext(bot); - context.temporaryUser = data.temporaryUser; - return context; - } -} - -class NoteContext extends Context { - public async greet(): Promise<string> { - return '内容:'; - } - - public async q(query: string): Promise<string> { - await require('../endpoints/notes/create')({ - text: query - }, this.bot.user); - this.bot.clearContext(); - return '投稿しましたよ!'; - } - - public export() { - return { - type: 'note' - }; - } - - public static import(bot: BotCore, data: any) { - const context = new NoteContext(bot); - return context; - } -} - -class TlContext extends Context { - private next: string = null; - - public async greet(): Promise<string> { - return await this.getTl(); - } - - public async q(query: string): Promise<string> { - if (query == '次') { - return await this.getTl(); - } else { - this.bot.clearContext(); - return await this.bot.q(query); - } - } - - private async getTl() { - const tl = await require('../endpoints/notes/timeline')({ - limit: 5, - untilId: this.next ? this.next : undefined - }, this.bot.user); - - if (tl.length > 0) { - this.next = tl[tl.length - 1].id; - this.emit('updated'); - - const text = tl - .map(note => `${getUserName(note.user)}\n「${getNoteSummary(note)}」`) - .join('\n-----\n'); - - return text; - } else { - return 'タイムラインに表示するものがありません...'; - } - } - - public export() { - return { - type: 'tl', - content: { - next: this.next, - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new TlContext(bot); - context.next = data.next; - return context; - } -} - -class NotificationsContext extends Context { - private next: string = null; - - public async greet(): Promise<string> { - return await this.getNotifications(); - } - - public async q(query: string): Promise<string> { - if (query == '次') { - return await this.getNotifications(); - } else { - this.bot.clearContext(); - return await this.bot.q(query); - } - } - - private async getNotifications() { - const notifications = await require('../endpoints/i/notifications')({ - limit: 5, - untilId: this.next ? this.next : undefined - }, this.bot.user); - - if (notifications.length > 0) { - this.next = notifications[notifications.length - 1].id; - this.emit('updated'); - - const text = notifications - .map(notification => getNotificationSummary(notification)) - .join('\n-----\n'); - - return text; - } else { - return '通知はありません'; - } - } - - public export() { - return { - type: 'notifications', - content: { - next: this.next, - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new NotificationsContext(bot); - context.next = data.next; - return context; - } -} - -class GuessingGameContext extends Context { - private secret: number; - private history: number[] = []; - - public async greet(): Promise<string> { - this.secret = Math.floor(Math.random() * 100); - this.emit('updated'); - return '0~100の秘密の数を当ててみてください:'; - } - - public async q(query: string): Promise<string> { - if (query == 'やめる') { - this.bot.clearContext(); - return 'やめました。'; - } - - const guess = parseInt(query, 10); - - if (isNaN(guess)) { - return '整数で推測してください。「やめる」と言うとゲームをやめます。'; - } - - const firsttime = this.history.indexOf(guess) === -1; - - this.history.push(guess); - this.emit('updated'); - - if (this.secret < guess) { - return firsttime ? `${guess}よりも小さいですね` : `もう一度言いますが${guess}より小さいですよ`; - } else if (this.secret > guess) { - return firsttime ? `${guess}よりも大きいですね` : `もう一度言いますが${guess}より大きいですよ`; - } else { - this.bot.clearContext(); - return `正解です🎉 (${this.history.length}回目で当てました)`; - } - } - - public export() { - return { - type: 'guessing-game', - content: { - secret: this.secret, - history: this.history - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new GuessingGameContext(bot); - context.secret = data.secret; - context.history = data.history; - return context; - } -} diff --git a/src/server/api/bot/interfaces/line.ts b/src/server/api/bot/interfaces/line.ts deleted file mode 100644 index 733315391d..0000000000 --- a/src/server/api/bot/interfaces/line.ts +++ /dev/null @@ -1,238 +0,0 @@ -import * as EventEmitter from 'events'; -import * as Router from 'koa-router'; -import * as request from 'request'; -import * as crypto from 'crypto'; -import User from '../../../../models/user'; -import config from '../../../../config'; -import BotCore from '../core'; -import _redis from '../../../../db/redis'; -import prominence = require('prominence'); -import getAcct from '../../../../acct/render'; -import parseAcct from '../../../../acct/parse'; -import getNoteSummary from '../../../../renderers/get-note-summary'; -import getUserName from '../../../../renderers/get-user-name'; - -const redis = prominence(_redis); - -// SEE: https://developers.line.me/media/messaging-api/messages/sticker_list.pdf -const stickers = [ - '297', - '298', - '299', - '300', - '301', - '302', - '303', - '304', - '305', - '306', - '307' -]; - -class LineBot extends BotCore { - private replyToken: string; - - private reply(messages: any[]) { - request.post({ - url: 'https://api.line.me/v2/bot/message/reply', - headers: { - 'Authorization': `Bearer ${config.line_bot.channel_access_token}` - }, - json: { - replyToken: this.replyToken, - messages: messages - } - }, (err, res, body) => { - if (err) { - console.error(err); - return; - } - }); - } - - public async react(ev: any): Promise<void> { - this.replyToken = ev.replyToken; - - switch (ev.type) { - // メッセージ - case 'message': - switch (ev.message.type) { - // テキスト - case 'text': - const res = await this.q(ev.message.text); - if (res == null) return; - // 返信 - this.reply([{ - type: 'text', - text: res - }]); - break; - - // スタンプ - case 'sticker': - // スタンプで返信 - this.reply([{ - type: 'sticker', - packageId: '4', - stickerId: stickers[Math.floor(Math.random() * stickers.length)] - }]); - break; - } - break; - - // noteback - case 'noteback': - const data = ev.noteback.data; - const cmd = data.split('|')[0]; - const arg = data.split('|')[1]; - switch (cmd) { - case 'showtl': - this.showUserTimelineNoteback(arg); - break; - } - break; - } - } - - public static import(data) { - const bot = new LineBot(); - bot._import(data); - return bot; - } - - public async showUserCommand(q: string) { - const user = await require('../../endpoints/users/show')(parseAcct(q.substr(1)), this.user); - - const acct = getAcct(user); - const actions = []; - - actions.push({ - type: 'noteback', - label: 'タイムラインを見る', - data: `showtl|${user.id}` - }); - - if (user.twitter) { - actions.push({ - type: 'uri', - label: 'Twitterアカウントを見る', - uri: `https://twitter.com/${user.twitter.screenName}` - }); - } - - actions.push({ - type: 'uri', - label: 'Webで見る', - uri: `${config.url}/@${acct}` - }); - - this.reply([{ - type: 'template', - altText: await super.showUserCommand(q), - template: { - type: 'buttons', - thumbnailImageUrl: `${user.avatarUrl}?thumbnail&size=1024`, - title: `${getUserName(user)} (@${acct})`, - text: user.description || '(no description)', - actions: actions - } - }]); - - return null; - } - - public async showUserTimelineNoteback(userId: string) { - const tl = await require('../../endpoints/users/notes')({ - userId: userId, - limit: 5 - }, this.user); - - const text = `${getUserName(tl[0].user)}さんのタイムラインはこちらです:\n\n` + tl - .map(note => getNoteSummary(note)) - .join('\n-----\n'); - - this.reply([{ - type: 'text', - text: text - }]); - } -} - -const handler = new EventEmitter(); - -handler.on('event', async (ev) => { - - const sourceId = ev.source.userId; - const sessionId = `line-bot-sessions:${sourceId}`; - - const session = await redis.get(sessionId); - let bot: LineBot; - - if (session == null) { - const user = await User.findOne({ - host: null, - 'line': { - userId: sourceId - } - }); - - bot = new LineBot(user); - - bot.on('signin', user => { - User.update(user._id, { - $set: { - 'line': { - userId: sourceId - } - } - }); - }); - - bot.on('signout', user => { - User.update(user._id, { - $set: { - 'line': { - userId: null - } - } - }); - }); - - redis.set(sessionId, JSON.stringify(bot.export())); - } else { - bot = LineBot.import(JSON.parse(session)); - } - - bot.on('updated', () => { - redis.set(sessionId, JSON.stringify(bot.export())); - }); - - if (session != null) bot.refreshUser(); - - bot.react(ev); -}); - -// Init router -const router = new Router(); - -if (config.line_bot) { - router.post('/hooks/line', ctx => { - const sig1 = ctx.headers['x-line-signature']; - - const hash = crypto.createHmac('SHA256', config.line_bot.channel_secret) - .update(ctx.request.rawBody); - - const sig2 = hash.digest('base64'); - - // シグネチャ比較 - if (sig1 === sig2) { - ctx.request.body.events.forEach(ev => { - handler.emit('event', ev); - }); - } else { - ctx.status = 400; - } - }); -} - -module.exports = router; diff --git a/src/server/api/common/get-host-lower.ts b/src/server/api/common/get-host-lower.ts index 550c233001..92704b4aa9 100644 --- a/src/server/api/common/get-host-lower.ts +++ b/src/server/api/common/get-host-lower.ts @@ -1,5 +1,5 @@ import { toUnicode } from 'punycode'; -export default host => { +export default (host: string) => { return toUnicode(host).toLowerCase(); }; diff --git a/src/server/api/common/read-messaging-message.ts b/src/server/api/common/read-messaging-message.ts index 28854e186e..fd5e9f242c 100644 --- a/src/server/api/common/read-messaging-message.ts +++ b/src/server/api/common/read-messaging-message.ts @@ -4,6 +4,7 @@ import { IMessagingMessage as IMessage } from '../../../models/messaging-message import publishUserStream from '../../../publishers/stream'; import { publishMessagingStream } from '../../../publishers/stream'; import { publishMessagingIndexStream } from '../../../publishers/stream'; +import User from '../../../models/user'; /** * Mark as read message(s) @@ -62,6 +63,13 @@ export default ( }); if (count == 0) { + // Update flag + User.update({ _id: userId }, { + $set: { + hasUnreadMessagingMessage: false + } + }); + // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 publishUserStream(userId, 'read_all_messaging_messages'); } diff --git a/src/server/api/common/read-notification.ts b/src/server/api/common/read-notification.ts index cdb87a4114..6505c58c39 100644 --- a/src/server/api/common/read-notification.ts +++ b/src/server/api/common/read-notification.ts @@ -2,6 +2,7 @@ import * as mongo from 'mongodb'; import { default as Notification, INotification } from '../../../models/notification'; import publishUserStream from '../../../publishers/stream'; import Mute from '../../../models/mute'; +import User from '../../../models/user'; /** * Mark as read notification(s) @@ -57,6 +58,13 @@ export default ( }); if (count == 0) { + // Update flag + User.update({ _id: userId }, { + $set: { + hasUnreadNotification: false + } + }); + // 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行 publishUserStream(userId, 'read_all_notifications'); } diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index 892da3540f..a5d13b0237 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -190,6 +190,11 @@ const endpoints: Endpoint[] = [ secure: true }, { + name: 'i/update_widget', + withCredential: true, + secure: true + }, + { name: 'i/change_password', withCredential: true, secure: true @@ -240,27 +245,27 @@ const endpoints: Endpoint[] = [ }, { - name: 'othello/match', + name: 'reversi/match', withCredential: true }, { - name: 'othello/match/cancel', + name: 'reversi/match/cancel', withCredential: true }, { - name: 'othello/invitations', + name: 'reversi/invitations', withCredential: true }, { - name: 'othello/games', + name: 'reversi/games', withCredential: true }, { - name: 'othello/games/show' + name: 'reversi/games/show' }, { @@ -280,11 +285,6 @@ const endpoints: Endpoint[] = [ }, { - name: 'notifications/get_unread_count', - withCredential: true, - kind: 'notification-read' - }, - { name: 'notifications/delete', withCredential: true, kind: 'notification-write' @@ -454,6 +454,26 @@ const endpoints: Endpoint[] = [ kind: 'following-write' }, { + name: 'following/requests/accept', + withCredential: true, + kind: 'following-write' + }, + { + name: 'following/requests/reject', + withCredential: true, + kind: 'following-write' + }, + { + name: 'following/requests/cancel', + withCredential: true, + kind: 'following-write' + }, + { + name: 'following/requests/list', + withCredential: true, + kind: 'following-read' + }, + { name: 'following/stalk', withCredential: true, limit: { @@ -495,12 +515,20 @@ const endpoints: Endpoint[] = [ kind: 'note-write' }, { + name: 'notes/delete', + withCredential: true, + kind: 'note-write' + }, + { name: 'notes/renotes' }, { name: 'notes/search' }, { + name: 'notes/search_by_tag' + }, + { name: 'notes/timeline', withCredential: true, limit: { @@ -510,7 +538,6 @@ const endpoints: Endpoint[] = [ }, { name: 'notes/local-timeline', - withCredential: true, limit: { duration: ms('10minutes'), max: 100 @@ -518,7 +545,6 @@ const endpoints: Endpoint[] = [ }, { name: 'notes/global-timeline', - withCredential: true, limit: { duration: ms('10minutes'), max: 100 @@ -557,7 +583,7 @@ const endpoints: Endpoint[] = [ withCredential: true, limit: { duration: ms('1hour'), - max: 100 + max: 300 }, kind: 'reaction-write' }, @@ -603,12 +629,12 @@ const endpoints: Endpoint[] = [ }, { - name: 'messaging/history', - withCredential: true, - kind: 'messaging-read' + name: 'hashtags/trend', + withCredential: true }, + { - name: 'messaging/unread', + name: 'messaging/history', withCredential: true, kind: 'messaging-read' }, diff --git a/src/server/api/endpoints/aggregation/posts.ts b/src/server/api/endpoints/aggregation/posts.ts index d348cadae9..48e344312d 100644 --- a/src/server/api/endpoints/aggregation/posts.ts +++ b/src/server/api/endpoints/aggregation/posts.ts @@ -1,13 +1,10 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import Note from '../../../../models/note'; /** * Aggregate notes */ -module.exports = params => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit); if (limitErr) return rej('invalid limit param'); @@ -52,13 +49,13 @@ module.exports = params => new Promise(async (res, rej) => { } } ]); - datas.forEach(data => { + datas.forEach((data: any) => { data.date = data._id; delete data._id; - data.notes = (data.data.filter(x => x.type == 'note')[0] || { count: 0 }).count; - data.renotes = (data.data.filter(x => x.type == 'renote')[0] || { count: 0 }).count; - data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count; + data.notes = (data.data.filter((x: any) => x.type == 'note')[0] || { count: 0 }).count; + data.renotes = (data.data.filter((x: any) => x.type == 'renote')[0] || { count: 0 }).count; + data.replies = (data.data.filter((x: any) => x.type == 'reply')[0] || { count: 0 }).count; delete data.data; }); @@ -68,7 +65,7 @@ module.exports = params => new Promise(async (res, rej) => { for (let i = 0; i < limit; i++) { const day = new Date(new Date().setDate(new Date().getDate() - i)); - const data = datas.filter(d => + const data = datas.filter((d: any) => d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() )[0]; diff --git a/src/server/api/endpoints/aggregation/users.ts b/src/server/api/endpoints/aggregation/users.ts index b116c1454b..c084404d0a 100644 --- a/src/server/api/endpoints/aggregation/users.ts +++ b/src/server/api/endpoints/aggregation/users.ts @@ -1,13 +1,10 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import User from '../../../../models/user'; /** * Aggregate users */ -module.exports = params => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/aggregation/users/activity.ts b/src/server/api/endpoints/aggregation/users/activity.ts index 9109487ac6..d4c716d65b 100644 --- a/src/server/api/endpoints/aggregation/users/activity.ts +++ b/src/server/api/endpoints/aggregation/users/activity.ts @@ -1,6 +1,3 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import User from '../../../../../models/user'; import Note from '../../../../../models/note'; @@ -10,7 +7,7 @@ import Note from '../../../../../models/note'; /** * Aggregate activity of a user */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit); if (limitErr) return rej('invalid limit param'); @@ -73,13 +70,13 @@ module.exports = (params) => new Promise(async (res, rej) => { } } ]); - datas.forEach(data => { + datas.forEach((data: any) => { data.date = data._id; delete data._id; - data.notes = (data.data.filter(x => x.type == 'note')[0] || { count: 0 }).count; - data.renotes = (data.data.filter(x => x.type == 'renote')[0] || { count: 0 }).count; - data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count; + data.notes = (data.data.filter((x: any) => x.type == 'note')[0] || { count: 0 }).count; + data.renotes = (data.data.filter((x: any) => x.type == 'renote')[0] || { count: 0 }).count; + data.replies = (data.data.filter((x: any) => x.type == 'reply')[0] || { count: 0 }).count; delete data.data; }); @@ -89,7 +86,7 @@ module.exports = (params) => new Promise(async (res, rej) => { for (let i = 0; i < limit; i++) { const day = new Date(new Date().setDate(new Date().getDate() - i)); - const data = datas.filter(d => + const data = datas.filter((d: any) => d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() )[0]; diff --git a/src/server/api/endpoints/aggregation/users/followers.ts b/src/server/api/endpoints/aggregation/users/followers.ts index dfcaf8462f..847f376079 100644 --- a/src/server/api/endpoints/aggregation/users/followers.ts +++ b/src/server/api/endpoints/aggregation/users/followers.ts @@ -8,7 +8,7 @@ import FollowedLog from '../../../../../models/followed-log'; /** * Aggregate followers of a user */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/aggregation/users/following.ts b/src/server/api/endpoints/aggregation/users/following.ts index 5f826fd71c..6c52752f98 100644 --- a/src/server/api/endpoints/aggregation/users/following.ts +++ b/src/server/api/endpoints/aggregation/users/following.ts @@ -8,7 +8,7 @@ import FollowingLog from '../../../../../models/following-log'; /** * Aggregate following of a user */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/aggregation/users/post.ts b/src/server/api/endpoints/aggregation/users/post.ts index 11f9ef14cd..28ba1482bf 100644 --- a/src/server/api/endpoints/aggregation/users/post.ts +++ b/src/server/api/endpoints/aggregation/users/post.ts @@ -1,6 +1,3 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import User from '../../../../../models/user'; import Note from '../../../../../models/note'; @@ -8,7 +5,7 @@ import Note from '../../../../../models/note'; /** * Aggregate note of a user */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); @@ -67,13 +64,13 @@ module.exports = (params) => new Promise(async (res, rej) => { } } ]); - datas.forEach(data => { + datas.forEach((data: any) => { data.date = data._id; delete data._id; - data.notes = (data.data.filter(x => x.type == 'note')[0] || { count: 0 }).count; - data.renotes = (data.data.filter(x => x.type == 'renote')[0] || { count: 0 }).count; - data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count; + data.notes = (data.data.filter((x: any) => x.type == 'note')[0] || { count: 0 }).count; + data.renotes = (data.data.filter((x: any) => x.type == 'renote')[0] || { count: 0 }).count; + data.replies = (data.data.filter((x: any) => x.type == 'reply')[0] || { count: 0 }).count; delete data.data; }); @@ -83,7 +80,7 @@ module.exports = (params) => new Promise(async (res, rej) => { for (let i = 0; i < 30; i++) { const day = new Date(new Date().setDate(new Date().getDate() - i)); - const data = datas.filter(d => + const data = datas.filter((d: any) => d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() )[0]; diff --git a/src/server/api/endpoints/aggregation/users/reaction.ts b/src/server/api/endpoints/aggregation/users/reaction.ts index 2de2840258..adb5acfb4e 100644 --- a/src/server/api/endpoints/aggregation/users/reaction.ts +++ b/src/server/api/endpoints/aggregation/users/reaction.ts @@ -1,17 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import User from '../../../../../models/user'; import Reaction from '../../../../../models/note-reaction'; /** * Aggregate reaction of a user - * - * @param {any} params - * @return {Promise<any>} */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); @@ -48,7 +42,7 @@ module.exports = (params) => new Promise(async (res, rej) => { }} ]); - datas.forEach(data => { + datas.forEach((data: any) => { data.date = data._id; delete data._id; }); @@ -58,7 +52,7 @@ module.exports = (params) => new Promise(async (res, rej) => { for (let i = 0; i < 30; i++) { const day = new Date(new Date().setDate(new Date().getDate() - i)); - const data = datas.filter(d => + const data = datas.filter((d: any) => d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() )[0]; diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts index 553bd2381a..c7bc91a079 100644 --- a/src/server/api/endpoints/app/create.ts +++ b/src/server/api/endpoints/app/create.ts @@ -1,9 +1,7 @@ -/** - * Module dependencies - */ import rndstr from 'rndstr'; import $ from 'cafy'; import App, { isValidNameId, pack } from '../../../../models/app'; +import { ILocalUser } from '../../../../models/user'; /** * @swagger @@ -60,12 +58,8 @@ import App, { isValidNameId, pack } from '../../../../models/app'; /** * Create an app - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'nameId' parameter const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId); if (nameIdErr) return rej('invalid nameId param'); diff --git a/src/server/api/endpoints/app/name_id/available.ts b/src/server/api/endpoints/app/name_id/available.ts index 135bb7d2b4..58101a7e6a 100644 --- a/src/server/api/endpoints/app/name_id/available.ts +++ b/src/server/api/endpoints/app/name_id/available.ts @@ -40,7 +40,7 @@ import { isValidNameId } from '../../../../../models/app'; * @param {any} params * @return {Promise<any>} */ -module.exports = async (params) => new Promise(async (res, rej) => { +module.exports = async (params: any) => new Promise(async (res, rej) => { // Get 'nameId' parameter const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId); if (nameIdErr) return rej('invalid nameId param'); diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts index 8d742ab182..2b98a3f142 100644 --- a/src/server/api/endpoints/app/show.ts +++ b/src/server/api/endpoints/app/show.ts @@ -1,8 +1,6 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import App, { pack } from '../../../../models/app'; +import App, { pack, IApp } from '../../../../models/app'; +import { ILocalUser } from '../../../../models/user'; /** * @swagger @@ -37,7 +35,7 @@ import App, { pack } from '../../../../models/app'; /** * Show an app */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const isSecure = user != null && app == null; // Get 'appId' parameter diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index 695fbb0803..fc6cbc473d 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -1,12 +1,10 @@ -/** - * Module dependencies - */ import rndstr from 'rndstr'; const crypto = require('crypto'); import $ from 'cafy'; import App from '../../../../models/app'; import AuthSess from '../../../../models/auth-session'; import AccessToken from '../../../../models/access-token'; +import { ILocalUser } from '../../../../models/user'; /** * @swagger @@ -33,12 +31,8 @@ import AccessToken from '../../../../models/access-token'; /** * Accept - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'token' parameter const [token, tokenErr] = $.str.get(params.token); if (tokenErr) return rej('invalid token param'); diff --git a/src/server/api/endpoints/auth/session/generate.ts b/src/server/api/endpoints/auth/session/generate.ts index d649a8d902..5a4d99ff3b 100644 --- a/src/server/api/endpoints/auth/session/generate.ts +++ b/src/server/api/endpoints/auth/session/generate.ts @@ -44,7 +44,7 @@ import config from '../../../../../config'; * @param {any} params * @return {Promise<any>} */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'appSecret' parameter const [appSecret, appSecretErr] = $.str.get(params.appSecret); if (appSecretErr) return rej('invalid appSecret param'); diff --git a/src/server/api/endpoints/auth/session/show.ts b/src/server/api/endpoints/auth/session/show.ts index 434cc264a0..3d3b6bbf61 100644 --- a/src/server/api/endpoints/auth/session/show.ts +++ b/src/server/api/endpoints/auth/session/show.ts @@ -1,8 +1,6 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import AuthSess, { pack } from '../../../../../models/auth-session'; +import { ILocalUser } from '../../../../../models/user'; /** * @swagger @@ -46,12 +44,8 @@ import AuthSess, { pack } from '../../../../../models/auth-session'; /** * Show a session - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'token' parameter const [token, tokenErr] = $.str.get(params.token); if (tokenErr) return rej('invalid token param'); diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts index 3026b477f1..3ea48fbe34 100644 --- a/src/server/api/endpoints/auth/session/userkey.ts +++ b/src/server/api/endpoints/auth/session/userkey.ts @@ -49,7 +49,7 @@ import { pack } from '../../../../../models/user'; * @param {any} params * @return {Promise<any>} */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'appSecret' parameter const [appSecret, appSecretErr] = $.str.get(params.appSecret); if (appSecretErr) return rej('invalid appSecret param'); diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts index d77ab2bbb0..9caad273c8 100644 --- a/src/server/api/endpoints/drive.ts +++ b/src/server/api/endpoints/drive.ts @@ -1,34 +1,33 @@ -/** - * Module dependencies - */ import DriveFile from '../../../models/drive-file'; +import { ILocalUser } from '../../../models/user'; /** * Get drive information - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Calculate drive usage - const usage = ((await DriveFile - .aggregate([ - { $match: { 'metadata.userId': user._id } }, - { - $project: { - length: true - } - }, - { - $group: { - _id: null, - usage: { $sum: '$length' } - } + const usage = await DriveFile + .aggregate([{ + $match: { + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } + } + }, { + $project: { + length: true + } + }, { + $group: { + _id: null, + usage: { $sum: '$length' } + } + }]) + .then((aggregates: any[]) => { + if (aggregates.length > 0) { + return aggregates[0].usage; } - ]))[0] || { - usage: 0 - }).usage; + return 0; + }); res({ capacity: user.driveCapacity, diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index ab4b18cef4..efce750747 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import DriveFile, { pack } from '../../../../models/drive-file'; +import { ILocalUser } from '../../../../models/user'; /** * Get drive files */ -module.exports = async (params, user, app) => { +module.exports = async (params: any, user: ILocalUser) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; @@ -37,10 +35,13 @@ module.exports = async (params, user, app) => { const sort = { _id: -1 }; + const query = { 'metadata.userId': user._id, - 'metadata.folderId': folderId + 'metadata.folderId': folderId, + 'metadata.deletedAt': { $exists: false } } as any; + if (sinceId) { sort._id = 1; query._id = { @@ -51,6 +52,7 @@ module.exports = async (params, user, app) => { $lt: untilId }; } + if (type) { query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); } diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index dd748d6bba..db2626af09 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -1,15 +1,13 @@ -/** - * Module dependencies - */ import * as fs from 'fs'; import $ from 'cafy'; import ID from '../../../../../cafy-id'; import { validateFileName, pack } from '../../../../../models/drive-file'; import create from '../../../../../services/drive/add-file'; +import { ILocalUser } from '../../../../../models/user'; /** * Create a file */ -module.exports = async (file, params, user): Promise<any> => { +module.exports = async (file: any, params: any, user: ILocalUser): Promise<any> => { if (file == null) { throw 'file is required'; } diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts new file mode 100644 index 0000000000..17eb0eb4b9 --- /dev/null +++ b/src/server/api/endpoints/drive/files/delete.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import DriveFile from '../../../../../models/drive-file'; +import del from '../../../../../services/drive/delete-file'; +import { publishDriveStream } from '../../../../../publishers/stream'; +import { ILocalUser } from '../../../../../models/user'; + +/** + * Delete a file + */ +module.exports = async (params: any, user: ILocalUser) => { + // Get 'fileId' parameter + const [fileId, fileIdErr] = $.type(ID).get(params.fileId); + if (fileIdErr) throw 'invalid fileId param'; + + // Fetch file + const file = await DriveFile + .findOne({ + _id: fileId, + 'metadata.userId': user._id + }); + + if (file === null) { + throw 'file-not-found'; + } + + // Delete + await del(file); + + // Publish file_deleted event + publishDriveStream(user._id, 'file_deleted', file._id); + + return; +}; diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts index 98165990fe..75ab91f0a1 100644 --- a/src/server/api/endpoints/drive/files/find.ts +++ b/src/server/api/endpoints/drive/files/find.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import DriveFile, { pack } from '../../../../../models/drive-file'; +import { ILocalUser } from '../../../../../models/user'; /** * Find a file(s) */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $.str.get(params.name); if (nameErr) return rej('invalid name param'); diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index c7efda7ab0..e7dca486c5 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import DriveFile, { pack } from '../../../../../models/drive-file'; +import { ILocalUser } from '../../../../../models/user'; /** * Show a file */ -module.exports = async (params, user) => { +module.exports = async (params: any, user: ILocalUser) => { // Get 'fileId' parameter const [fileId, fileIdErr] = $.type(ID).get(params.fileId); if (fileIdErr) throw 'invalid fileId param'; diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index 12fa8e025d..825683b214 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -1,15 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import DriveFolder from '../../../../../models/drive-folder'; import DriveFile, { validateFileName, pack } from '../../../../../models/drive-file'; import { publishDriveStream } from '../../../../../publishers/stream'; +import { ILocalUser } from '../../../../../models/user'; /** * Update a file */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'fileId' parameter const [fileId, fileIdErr] = $.type(ID).get(params.fileId); if (fileIdErr) return rej('invalid fileId param'); diff --git a/src/server/api/endpoints/drive/files/upload_from_url.ts b/src/server/api/endpoints/drive/files/upload_from_url.ts index c012f0d3c9..cb617d851f 100644 --- a/src/server/api/endpoints/drive/files/upload_from_url.ts +++ b/src/server/api/endpoints/drive/files/upload_from_url.ts @@ -4,11 +4,12 @@ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import { pack } from '../../../../../models/drive-file'; import uploadFromUrl from '../../../../../services/drive/upload-from-url'; +import { ILocalUser } from '../../../../../models/user'; /** * Create a file from a URL */ -module.exports = async (params, user): Promise<any> => { +module.exports = async (params: any, user: ILocalUser): Promise<any> => { // Get 'url' parameter // TODO: Validate this url const [url, urlErr] = $.str.get(params.url); diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index bc6c50eb99..3413778950 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import DriveFolder, { pack } from '../../../../models/drive-folder'; +import { ILocalUser } from '../../../../models/user'; /** * Get drive folders */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts index 62e3b6f6e8..8f06b0f668 100644 --- a/src/server/api/endpoints/drive/folders/create.ts +++ b/src/server/api/endpoints/drive/folders/create.ts @@ -1,14 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; import { publishDriveStream } from '../../../../../publishers/stream'; +import { ILocalUser } from '../../../../../models/user'; /** * Create drive folder */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter const [name = '無題のフォルダー', nameErr] = $.str.optional().pipe(isValidFolderName).get(params.name); if (nameErr) return rej('invalid name param'); diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts index 9703d9e99d..b3238b5c32 100644 --- a/src/server/api/endpoints/drive/folders/find.ts +++ b/src/server/api/endpoints/drive/folders/find.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import DriveFolder, { pack } from '../../../../../models/drive-folder'; +import { ILocalUser } from '../../../../../models/user'; /** * Find a folder(s) */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $.str.get(params.name); if (nameErr) return rej('invalid name param'); diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts index 44f1889001..c9b4930a76 100644 --- a/src/server/api/endpoints/drive/folders/show.ts +++ b/src/server/api/endpoints/drive/folders/show.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import DriveFolder, { pack } from '../../../../../models/drive-folder'; +import { ILocalUser } from '../../../../../models/user'; /** * Show a folder */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'folderId' parameter const [folderId, folderIdErr] = $.type(ID).get(params.folderId); if (folderIdErr) return rej('invalid folderId param'); diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index e24d8a14cd..f126c09f5b 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -1,14 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; import { publishDriveStream } from '../../../../../publishers/stream'; +import { ILocalUser } from '../../../../../models/user'; /** * Update a folder */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'folderId' parameter const [folderId, folderIdErr] = $.type(ID).get(params.folderId); if (folderIdErr) return rej('invalid folderId param'); @@ -48,7 +46,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { } // Check if the circular reference will occur - async function checkCircle(folderId) { + async function checkCircle(folderId: any): Promise<boolean> { // Fetch folder const folder2 = await DriveFolder.findOne({ _id: folderId diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index 8cb3a99b42..515f74645a 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import DriveFile, { pack } from '../../../../models/drive-file'; +import { ILocalUser } from '../../../../models/user'; /** * Get drive stream */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index 766a8c03d0..3e45b8da53 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -1,15 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User from '../../../../models/user'; +import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import create from '../../../../services/following/create'; /** * Follow a user */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter @@ -49,5 +46,5 @@ module.exports = (params, user) => new Promise(async (res, rej) => { create(follower, followee); // Send response - res(); + res(await pack(followee._id, user)); }); diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts index 396b19a6f6..0af8813cf9 100644 --- a/src/server/api/endpoints/following/delete.ts +++ b/src/server/api/endpoints/following/delete.ts @@ -1,15 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User from '../../../../models/user'; +import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import deleteFollowing from '../../../../services/following/delete'; /** * Unfollow a user */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter @@ -49,5 +46,5 @@ module.exports = (params, user) => new Promise(async (res, rej) => { deleteFollowing(follower, followee); // Send response - res(); + res(await pack(followee._id, user)); }); diff --git a/src/server/api/endpoints/following/requests/accept.ts b/src/server/api/endpoints/following/requests/accept.ts new file mode 100644 index 0000000000..a09e32e4d9 --- /dev/null +++ b/src/server/api/endpoints/following/requests/accept.ts @@ -0,0 +1,26 @@ +import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import acceptFollowRequest from '../../../../../services/following/requests/accept'; +import User, { ILocalUser } from '../../../../../models/user'; + +/** + * Accept a follow request + */ +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Get 'userId' parameter + const [followerId, followerIdErr] = $.type(ID).get(params.userId); + if (followerIdErr) return rej('invalid userId param'); + + // Fetch follower + const follower = await User.findOne({ + _id: followerId + }); + + if (follower === null) { + return rej('follower not found'); + } + + await acceptFollowRequest(user, follower); + + // Send response + res(); +}); diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts new file mode 100644 index 0000000000..f2a40854c2 --- /dev/null +++ b/src/server/api/endpoints/following/requests/cancel.ts @@ -0,0 +1,26 @@ +import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import cancelFollowRequest from '../../../../../services/following/requests/cancel'; +import User, { pack, ILocalUser } from '../../../../../models/user'; + +/** + * Cancel a follow request + */ +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Get 'userId' parameter + const [followeeId, followeeIdErr] = $.type(ID).get(params.userId); + if (followeeIdErr) return rej('invalid userId param'); + + // Fetch followee + const followee = await User.findOne({ + _id: followeeId + }); + + if (followee === null) { + return rej('followee not found'); + } + + await cancelFollowRequest(followee, user); + + // Send response + res(await pack(followee._id, user)); +}); diff --git a/src/server/api/endpoints/following/requests/list.ts b/src/server/api/endpoints/following/requests/list.ts new file mode 100644 index 0000000000..287c5a8e46 --- /dev/null +++ b/src/server/api/endpoints/following/requests/list.ts @@ -0,0 +1,15 @@ +//import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import FollowRequest, { pack } from '../../../../../models/follow-request'; +import { ILocalUser } from '../../../../../models/user'; + +/** + * Get all pending received follow requests + */ +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const reqs = await FollowRequest.find({ + followeeId: user._id + }); + + // Send response + res(await Promise.all(reqs.map(req => pack(req)))); +}); diff --git a/src/server/api/endpoints/following/requests/reject.ts b/src/server/api/endpoints/following/requests/reject.ts new file mode 100644 index 0000000000..69dddf1355 --- /dev/null +++ b/src/server/api/endpoints/following/requests/reject.ts @@ -0,0 +1,26 @@ +import $ from 'cafy'; import ID from '../../../../../cafy-id'; +import rejectFollowRequest from '../../../../../services/following/requests/reject'; +import User, { ILocalUser } from '../../../../../models/user'; + +/** + * Reject a follow request + */ +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Get 'userId' parameter + const [followerId, followerIdErr] = $.type(ID).get(params.userId); + if (followerIdErr) return rej('invalid userId param'); + + // Fetch follower + const follower = await User.findOne({ + _id: followerId + }); + + if (follower === null) { + return rej('follower not found'); + } + + await rejectFollowRequest(user, follower); + + // Send response + res(); +}); diff --git a/src/server/api/endpoints/following/stalk.ts b/src/server/api/endpoints/following/stalk.ts index f0bc8cbdfc..b9d19d57b4 100644 --- a/src/server/api/endpoints/following/stalk.ts +++ b/src/server/api/endpoints/following/stalk.ts @@ -1,10 +1,11 @@ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Following from '../../../../models/following'; +import { ILocalUser } from '../../../../models/user'; /** * Stalk a user */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/following/unstalk.ts b/src/server/api/endpoints/following/unstalk.ts index 0d0a018c34..255f22ca1f 100644 --- a/src/server/api/endpoints/following/unstalk.ts +++ b/src/server/api/endpoints/following/unstalk.ts @@ -1,10 +1,11 @@ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Following from '../../../../models/following'; +import { ILocalUser } from '../../../../models/user'; /** * Unstalk a user */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const follower = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts new file mode 100644 index 0000000000..17af9d6a9a --- /dev/null +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -0,0 +1,139 @@ +import Note from '../../../../models/note'; + +/* +トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 +ユニーク投稿数とはそのハッシュタグと投稿ユーザーのペアのカウントで、例えば同じユーザーが複数回同じハッシュタグを投稿してもそのハッシュタグのユニーク投稿数は1とカウントされる +*/ + +const rangeA = 1000 * 60 * 30; // 30分 +const rangeB = 1000 * 60 * 120; // 2時間 +const coefficient = 1.25; // 「n倍」の部分 +const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか + +const max = 5; + +/** + * Get trends of hashtags + */ +module.exports = () => new Promise(async (res, rej) => { + //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 + const data = await Note.aggregate([{ + $match: { + createdAt: { + $gt: new Date(Date.now() - rangeA) + }, + tagsLower: { + $exists: true, + $ne: [] + } + } + }, { + $unwind: '$tagsLower' + }, { + $group: { + _id: { tag: '$tagsLower', userId: '$userId' } + } + }]) as Array<{ + _id: { + tag: string; + userId: any; + } + }>; + //#endregion + + if (data.length == 0) { + return res([]); + } + + const tags: Array<{ + name: string; + count: number; + }> = []; + + // カウント + data.map(x => x._id).forEach(x => { + const i = tags.findIndex(tag => tag.name == x.tag); + if (i != -1) { + tags[i].count++; + } else { + tags.push({ + name: x.tag, + count: 1 + }); + } + }); + + // 最低要求投稿者数を下回るならカットする + const limitedTags = tags.filter(tag => tag.count >= requiredUsers); + + //#region 2. 1で取得したそれぞれのタグについて、「直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上」かどうかを判定する + const hotsPromises = limitedTags.map(async tag => { + const passedCount = (await Note.distinct('userId', { + tagsLower: tag.name, + createdAt: { + $lt: new Date(Date.now() - rangeA), + $gt: new Date(Date.now() - rangeB) + } + }) as any).length; + + if (tag.count >= (passedCount * coefficient)) { + return tag; + } else { + return null; + } + }); + //#endregion + + // タグを人気順に並べ替え + let hots = (await Promise.all(hotsPromises)) + .filter(x => x != null) + .sort((a, b) => b.count - a.count) + .map(tag => tag.name) + .slice(0, max); + + //#region 3. もし上記の方法でのトレンド抽出の結果、求められているタグ数に達しなければ「ただ単に現在投稿数が多いハッシュタグ」に切り替える + if (hots.length < max) { + hots = hots.concat(tags + .filter(tag => hots.indexOf(tag.name) == -1) + .sort((a, b) => b.count - a.count) + .map(tag => tag.name) + .slice(0, max - hots.length)); + } + //#endregion + + //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する + const countPromises: Array<Promise<any[]>> = []; + + const range = 20; + + // 10分 + const interval = 1000 * 60 * 10; + + for (let i = 0; i < range; i++) { + countPromises.push(Promise.all(hots.map(tag => Note.distinct('userId', { + tagsLower: tag, + createdAt: { + $lt: new Date(Date.now() - (interval * i)), + $gt: new Date(Date.now() - (interval * (i + 1))) + } + })))); + } + + const countsLog = await Promise.all(countPromises); + + const totalCounts: any = await Promise.all(hots.map(tag => Note.distinct('userId', { + tagsLower: tag, + createdAt: { + $gt: new Date(Date.now() - (interval * range)) + } + }))); + //#endregion + + const stats = hots.map((tag, i) => ({ + tag, + chart: countsLog.map(counts => counts[i].length), + usersCount: totalCounts[i].length + })); + + res(stats); +}); diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts index 379c3c4d88..5c769a02fd 100644 --- a/src/server/api/endpoints/i.ts +++ b/src/server/api/endpoints/i.ts @@ -1,12 +1,10 @@ -/** - * Module dependencies - */ -import User, { pack } from '../../../models/user'; +import User, { pack, ILocalUser } from '../../../models/user'; +import { IApp } from '../../../models/app'; /** * Show myself */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const isSecure = user != null && app == null; // Serialize diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index 1a2706aa84..61f13c4c61 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -1,11 +1,8 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; -import User from '../../../../../models/user'; +import User, { ILocalUser } from '../../../../../models/user'; -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'token' parameter const [token, tokenErr] = $.str.get(params.token); if (tokenErr) return rej('invalid token param'); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index d314e1a280..d05892c84b 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -1,14 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; -import User from '../../../../../models/user'; +import User, { ILocalUser } from '../../../../../models/user'; import config from '../../../../../config'; -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'password' parameter const [password, passwordErr] = $.str.get(params.password); if (passwordErr) return rej('invalid password param'); diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index 336a3564ab..fc197cb1e4 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,11 +1,8 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../../models/user'; +import User, { ILocalUser } from '../../../../../models/user'; -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'password' parameter const [password, passwordErr] = $.str.get(params.password); if (passwordErr) return rej('invalid password param'); diff --git a/src/server/api/endpoints/i/authorized_apps.ts b/src/server/api/endpoints/i/authorized_apps.ts index d15bd67bf2..cfc93c1518 100644 --- a/src/server/api/endpoints/i/authorized_apps.ts +++ b/src/server/api/endpoints/i/authorized_apps.ts @@ -1,14 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import AccessToken from '../../../../models/access-token'; import { pack } from '../../../../models/app'; +import { ILocalUser } from '../../../../models/user'; /** * Get authorized apps of my account */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/i/change_password.ts b/src/server/api/endpoints/i/change_password.ts index a1a1a43406..9851fa895a 100644 --- a/src/server/api/endpoints/i/change_password.ts +++ b/src/server/api/endpoints/i/change_password.ts @@ -1,14 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; /** * Change password */ -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'currentPasword' parameter const [currentPassword, currentPasswordErr] = $.str.get(params.currentPasword); if (currentPasswordErr) return rej('invalid currentPasword param'); diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index 23517baaff..dc343afaed 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Favorite, { pack } from '../../../../models/favorite'; +import { ILocalUser } from '../../../../models/user'; /** * Get favorited notes */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index ba9c47508c..ce283fe48f 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -1,17 +1,15 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Notification from '../../../../models/notification'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/notification'; import { getFriendIds } from '../../common/get-friends'; import read from '../../common/read-notification'; +import { ILocalUser } from '../../../../models/user'; /** * Get notifications */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'following' parameter const [following = false, followingError] = $.bool.optional().get(params.following); diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts index 423f0ac4ae..7f4a45e1f5 100644 --- a/src/server/api/endpoints/i/pin.ts +++ b/src/server/api/endpoints/i/pin.ts @@ -1,15 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Note from '../../../../models/note'; import { pack } from '../../../../models/user'; /** * Pin note */ -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/i/regenerate_token.ts b/src/server/api/endpoints/i/regenerate_token.ts index 6e1e571297..3ffab5428e 100644 --- a/src/server/api/endpoints/i/regenerate_token.ts +++ b/src/server/api/endpoints/i/regenerate_token.ts @@ -1,16 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import event from '../../../../publishers/stream'; import generateUserToken from '../../common/generate-native-user-token'; /** * Regenerate native token */ -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'password' parameter const [password, passwordErr] = $.str.get(params.password); if (passwordErr) return rej('invalid password param'); diff --git a/src/server/api/endpoints/i/signin_history.ts b/src/server/api/endpoints/i/signin_history.ts index 63a74b41b1..4ab9881f34 100644 --- a/src/server/api/endpoints/i/signin_history.ts +++ b/src/server/api/endpoints/i/signin_history.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Signin, { pack } from '../../../../models/signin'; +import { ILocalUser } from '../../../../models/user'; /** * Get signin history of my account */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 6e0c5b8515..57b050ebc4 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -1,61 +1,72 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack } from '../../../../models/user'; +import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack, ILocalUser } from '../../../../models/user'; import event from '../../../../publishers/stream'; import DriveFile from '../../../../models/drive-file'; +import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; +import { IApp } from '../../../../models/app'; /** * Update myself */ -module.exports = async (params, user, app) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const isSecure = user != null && app == null; + const updates = {} as any; + // Get 'name' parameter const [name, nameErr] = $.str.optional().nullable().pipe(isValidName).get(params.name); if (nameErr) return rej('invalid name param'); - if (name) user.name = name; + if (name) updates.name = name; // Get 'description' parameter const [description, descriptionErr] = $.str.optional().nullable().pipe(isValidDescription).get(params.description); if (descriptionErr) return rej('invalid description param'); - if (description !== undefined) user.description = description; + if (description !== undefined) updates.description = description; // Get 'location' parameter const [location, locationErr] = $.str.optional().nullable().pipe(isValidLocation).get(params.location); if (locationErr) return rej('invalid location param'); - if (location !== undefined) user.profile.location = location; + if (location !== undefined) updates['profile.location'] = location; // Get 'birthday' parameter const [birthday, birthdayErr] = $.str.optional().nullable().pipe(isValidBirthday).get(params.birthday); if (birthdayErr) return rej('invalid birthday param'); - if (birthday !== undefined) user.profile.birthday = birthday; + if (birthday !== undefined) updates['profile.birthday'] = birthday; // Get 'avatarId' parameter - const [avatarId, avatarIdErr] = $.type(ID).optional().get(params.avatarId); + const [avatarId, avatarIdErr] = $.type(ID).optional().nullable().get(params.avatarId); if (avatarIdErr) return rej('invalid avatarId param'); - if (avatarId) user.avatarId = avatarId; + if (avatarId !== undefined) updates.avatarId = avatarId; // Get 'bannerId' parameter - const [bannerId, bannerIdErr] = $.type(ID).optional().get(params.bannerId); + const [bannerId, bannerIdErr] = $.type(ID).optional().nullable().get(params.bannerId); if (bannerIdErr) return rej('invalid bannerId param'); - if (bannerId) user.bannerId = bannerId; + if (bannerId !== undefined) updates.bannerId = bannerId; + + // Get 'wallpaperId' parameter + const [wallpaperId, wallpaperIdErr] = $.type(ID).optional().nullable().get(params.wallpaperId); + if (wallpaperIdErr) return rej('invalid wallpaperId param'); + if (wallpaperId !== undefined) updates.wallpaperId = wallpaperId; + + // Get 'isLocked' parameter + const [isLocked, isLockedErr] = $.bool.optional().get(params.isLocked); + if (isLockedErr) return rej('invalid isLocked param'); + if (isLocked != null) updates.isLocked = isLocked; // Get 'isBot' parameter const [isBot, isBotErr] = $.bool.optional().get(params.isBot); if (isBotErr) return rej('invalid isBot param'); - if (isBot != null) user.isBot = isBot; + if (isBot != null) updates.isBot = isBot; // Get 'isCat' parameter const [isCat, isCatErr] = $.bool.optional().get(params.isCat); if (isCatErr) return rej('invalid isCat param'); - if (isCat != null) user.isCat = isCat; + if (isCat != null) updates.isCat = isCat; // Get 'autoWatch' parameter const [autoWatch, autoWatchErr] = $.bool.optional().get(params.autoWatch); if (autoWatchErr) return rej('invalid autoWatch param'); - if (autoWatch != null) user.settings.autoWatch = autoWatch; + if (autoWatch != null) updates['settings.autoWatch'] = autoWatch; if (avatarId) { const avatar = await DriveFile.findOne({ @@ -63,7 +74,7 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => { }); if (avatar != null && avatar.metadata.properties.avgColor) { - user.avatarColor = avatar.metadata.properties.avgColor; + updates.avatarColor = avatar.metadata.properties.avgColor; } } @@ -73,27 +84,26 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => { }); if (banner != null && banner.metadata.properties.avgColor) { - user.bannerColor = banner.metadata.properties.avgColor; + updates.bannerColor = banner.metadata.properties.avgColor; } } - await User.update(user._id, { - $set: { - name: user.name, - description: user.description, - avatarId: user.avatarId, - avatarColor: user.avatarColor, - bannerId: user.bannerId, - bannerColor: user.bannerColor, - profile: user.profile, - isBot: user.isBot, - isCat: user.isCat, - settings: user.settings + if (wallpaperId) { + const wallpaper = await DriveFile.findOne({ + _id: wallpaperId + }); + + if (wallpaper != null && wallpaper.metadata.properties.avgColor) { + updates.wallpaperColor = wallpaper.metadata.properties.avgColor; } + } + + await User.update(user._id, { + $set: updates }); // Serialize - const iObj = await pack(user, user, { + const iObj = await pack(user._id, user, { detail: true, includeSecrets: isSecure }); @@ -101,6 +111,11 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => { // Send response res(iObj); - // Publish i updated event - event(user._id, 'i_updated', iObj); + // Publish meUpdated event + event(user._id, 'meUpdated', iObj); + + // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 + if (user.isLocked && isLocked === false) { + acceptAllFollowRequests(user); + } }); diff --git a/src/server/api/endpoints/i/update_client_setting.ts b/src/server/api/endpoints/i/update_client_setting.ts index e91d7565fd..6d6e8ed24a 100644 --- a/src/server/api/endpoints/i/update_client_setting.ts +++ b/src/server/api/endpoints/i/update_client_setting.ts @@ -1,14 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import event from '../../../../publishers/stream'; /** * Update myself */ -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'name' parameter const [name, nameErr] = $.str.get(params.name); if (nameErr) return rej('invalid name param'); @@ -17,7 +14,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { const [value, valueErr] = $.any.nullable().get(params.value); if (valueErr) return rej('invalid value param'); - const x = {}; + const x: any = {}; x[`clientSettings.${name}`] = value; await User.update(user._id, { diff --git a/src/server/api/endpoints/i/update_home.ts b/src/server/api/endpoints/i/update_home.ts index 8ce551957e..511a647d88 100644 --- a/src/server/api/endpoints/i/update_home.ts +++ b/src/server/api/endpoints/i/update_home.ts @@ -1,11 +1,8 @@ -/** - * Module dependencies - */ import $ from 'cafy'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import event from '../../../../publishers/stream'; -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'home' parameter const [home, homeErr] = $.arr( $.obj.strict() @@ -13,50 +10,16 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { .have('id', $.str) .have('place', $.str) .have('data', $.obj)) - .optional() .get(params.home); if (homeErr) return rej('invalid home param'); - // Get 'id' parameter - const [id, idErr] = $.str.optional().get(params.id); - if (idErr) return rej('invalid id param'); + await User.update(user._id, { + $set: { + 'clientSettings.home': home + } + }); - // Get 'data' parameter - const [data, dataErr] = $.obj.optional().get(params.data); - if (dataErr) return rej('invalid data param'); + res(); - if (home) { - await User.update(user._id, { - $set: { - 'clientSettings.home': home - } - }); - - res(); - - event(user._id, 'home_updated', { - home - }); - } else { - if (id == null && data == null) return rej('you need to set id and data params if home param unset'); - - const _home = user.clientSettings.home; - const widget = _home.find(w => w.id == id); - - if (widget == null) return rej('widget not found'); - - widget.data = data; - - await User.update(user._id, { - $set: { - 'clientSettings.home': _home - } - }); - - res(); - - event(user._id, 'home_updated', { - id, data - }); - } + event(user._id, 'home_updated', home); }); diff --git a/src/server/api/endpoints/i/update_mobile_home.ts b/src/server/api/endpoints/i/update_mobile_home.ts index d79a77072b..b1f25624fd 100644 --- a/src/server/api/endpoints/i/update_mobile_home.ts +++ b/src/server/api/endpoints/i/update_mobile_home.ts @@ -1,60 +1,24 @@ -/** - * Module dependencies - */ import $ from 'cafy'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import event from '../../../../publishers/stream'; -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'home' parameter const [home, homeErr] = $.arr( $.obj.strict() .have('name', $.str) .have('id', $.str) .have('data', $.obj)) - .optional().get(params.home); + .get(params.home); if (homeErr) return rej('invalid home param'); - // Get 'id' parameter - const [id, idErr] = $.str.optional().get(params.id); - if (idErr) return rej('invalid id param'); + await User.update(user._id, { + $set: { + 'clientSettings.mobileHome': home + } + }); - // Get 'data' parameter - const [data, dataErr] = $.obj.optional().get(params.data); - if (dataErr) return rej('invalid data param'); + res(); - if (home) { - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': home - } - }); - - res(); - - event(user._id, 'mobile_home_updated', { - home - }); - } else { - if (id == null && data == null) return rej('you need to set id and data params if home param unset'); - - const _home = user.clientSettings.mobileHome || []; - const widget = _home.find(w => w.id == id); - - if (widget == null) return rej('widget not found'); - - widget.data = data; - - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': _home - } - }); - - res(); - - event(user._id, 'mobile_home_updated', { - id, data - }); - } + event(user._id, 'mobile_home_updated', home); }); diff --git a/src/server/api/endpoints/i/update_widget.ts b/src/server/api/endpoints/i/update_widget.ts new file mode 100644 index 0000000000..82bb04d1f4 --- /dev/null +++ b/src/server/api/endpoints/i/update_widget.ts @@ -0,0 +1,79 @@ +import $ from 'cafy'; +import User, { ILocalUser } from '../../../../models/user'; +import event from '../../../../publishers/stream'; + +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Get 'id' parameter + const [id, idErr] = $.str.get(params.id); + if (idErr) return rej('invalid id param'); + + // Get 'data' parameter + const [data, dataErr] = $.obj.get(params.data); + if (dataErr) return rej('invalid data param'); + + if (id == null && data == null) return rej('you need to set id and data params if home param unset'); + + let widget; + + //#region Desktop home + if (widget == null && user.clientSettings.home) { + const desktopHome = user.clientSettings.home; + widget = desktopHome.find((w: any) => w.id == id); + if (widget) { + widget.data = data; + + await User.update(user._id, { + $set: { + 'clientSettings.home': desktopHome + } + }); + } + } + //#endregion + + //#region Mobile home + if (widget == null && user.clientSettings.mobileHome) { + const mobileHome = user.clientSettings.mobileHome; + widget = mobileHome.find((w: any) => w.id == id); + if (widget) { + widget.data = data; + + await User.update(user._id, { + $set: { + 'clientSettings.mobileHome': mobileHome + } + }); + } + } + //#endregion + + //#region Deck + if (widget == null && user.clientSettings.deck && user.clientSettings.deck.columns) { + const deck = user.clientSettings.deck; + deck.columns.filter((c: any) => c.type == 'widgets').forEach((c: any) => { + c.widgets.forEach((w: any) => { + if (w.id == id) widget = w; + }); + }); + if (widget) { + widget.data = data; + + await User.update(user._id, { + $set: { + 'clientSettings.deck': deck + } + }); + } + } + //#endregion + + if (widget) { + event(user._id, 'widgetUpdated', { + id, data + }); + + res(); + } else { + rej('widget not found'); + } +}); diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts index ec97642f17..713ba9dd7f 100644 --- a/src/server/api/endpoints/messaging/history.ts +++ b/src/server/api/endpoints/messaging/history.ts @@ -1,15 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import History from '../../../../models/messaging-history'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/messaging-message'; +import { ILocalUser } from '../../../../models/user'; /** * Show messaging history */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts index 0338aba68a..3eb20ec06b 100644 --- a/src/server/api/endpoints/messaging/messages.ts +++ b/src/server/api/endpoints/messaging/messages.ts @@ -1,20 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Message from '../../../../models/messaging-message'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import { pack } from '../../../../models/messaging-message'; import read from '../../common/read-messaging-message'; /** * Get messages - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [recipientId, recipientIdErr] = $.type(ID).get(params.userId); if (recipientIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index db471839e7..b3e4f6a8cd 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -1,11 +1,8 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import Message from '../../../../../models/messaging-message'; import { isValidText } from '../../../../../models/messaging-message'; import History from '../../../../../models/messaging-history'; -import User from '../../../../../models/user'; +import User, { ILocalUser } from '../../../../../models/user'; import Mute from '../../../../../models/mute'; import DriveFile from '../../../../../models/drive-file'; import { pack } from '../../../../../models/messaging-message'; @@ -17,7 +14,7 @@ import config from '../../../../../config'; /** * Create a message */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [recipientId, recipientIdErr] = $.type(ID).get(params.userId); if (recipientIdErr) return rej('invalid userId param'); @@ -91,6 +88,13 @@ module.exports = (params, user) => new Promise(async (res, rej) => { publishMessagingIndexStream(message.recipientId, 'message', messageObj); publishUserStream(message.recipientId, 'messaging_message', messageObj); + // Update flag + User.update({ _id: recipient._id }, { + $set: { + hasUnreadMessagingMessage: true + } + }); + // 3秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する setTimeout(async () => { const freshMessage = await Message.findOne({ _id: message._id }, { isRead: true }); diff --git a/src/server/api/endpoints/messaging/unread.ts b/src/server/api/endpoints/messaging/unread.ts deleted file mode 100644 index 1d83af501d..0000000000 --- a/src/server/api/endpoints/messaging/unread.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Module dependencies - */ -import Message from '../../../../models/messaging-message'; -import Mute from '../../../../models/mute'; - -/** - * Get count of unread messages - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - const mute = await Mute.find({ - muterId: user._id, - deletedAt: { $exists: false } - }); - const mutedUserIds = mute.map(m => m.muteeId); - - const count = await Message - .count({ - userId: { - $nin: mutedUserIds - }, - recipientId: user._id, - isRead: false - }); - - res({ - count: count - }); -}); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 0e9ecf47df..ce460d0b8b 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -38,7 +38,7 @@ const client = require('../../../../built/client/meta.json'); /** * Show core info */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { const meta: any = (await Meta.findOne()) || {}; res({ diff --git a/src/server/api/endpoints/mute/create.ts b/src/server/api/endpoints/mute/create.ts index 534020c671..415745e2c3 100644 --- a/src/server/api/endpoints/mute/create.ts +++ b/src/server/api/endpoints/mute/create.ts @@ -1,14 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; /** * Mute a user */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const muter = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/mute/delete.ts b/src/server/api/endpoints/mute/delete.ts index 949aff64ba..2d1d286585 100644 --- a/src/server/api/endpoints/mute/delete.ts +++ b/src/server/api/endpoints/mute/delete.ts @@ -1,14 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; /** * Unmute a user */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const muter = user; // Get 'userId' parameter diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index cf89f7e959..8b0171be33 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -1,15 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/user'; +import { pack, ILocalUser } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; /** * Get muted users of a user */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'iknow' parameter const [iknow = false, iknowErr] = $.bool.optional().get(params.iknow); if (iknowErr) return rej('invalid iknow param'); diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts index 086e0b8965..7687afd0c7 100644 --- a/src/server/api/endpoints/my/apps.ts +++ b/src/server/api/endpoints/my/apps.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import App, { pack } from '../../../../models/app'; +import { ILocalUser } from '../../../../models/user'; /** * Get my apps */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 21946d1bd3..5554e53aa4 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -7,7 +7,7 @@ import Note, { pack } from '../../../models/note'; /** * Get all notes */ -module.exports = (params) => new Promise(async (res, rej) => { +module.exports = (params: any) => new Promise(async (res, rej) => { // Get 'local' parameter const [local, localErr] = $.bool.optional().get(params.local); if (localErr) return rej('invalid local param'); @@ -53,7 +53,9 @@ module.exports = (params) => new Promise(async (res, rej) => { const sort = { _id: -1 }; - const query = {} as any; + const query = { + visibility: 'public' + } as any; if (sinceId) { sort._id = 1; query._id = { diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index 02f7229ccf..b2bc6a2e72 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import Note, { pack } from '../../../../models/note'; +import Note, { pack, INote } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Show conversation of a note */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); @@ -29,10 +27,10 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('note not found'); } - const conversation = []; + const conversation: INote[] = []; let i = 0; - async function get(id) { + async function get(id: any) { i++; const p = await Note.findOne({ _id: id }); diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 182359f637..64f3b5ce26 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -1,9 +1,6 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note'; -import User, { ILocalUser } from '../../../../models/user'; +import User, { ILocalUser, IUser } from '../../../../models/user'; import DriveFile from '../../../../models/drive-file'; import create from '../../../../services/note/create'; import { IApp } from '../../../../models/app'; @@ -11,7 +8,7 @@ import { IApp } from '../../../../models/app'; /** * Create a note */ -module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { // Get 'visibility' parameter const [visibility = 'public', visibilityErr] = $.str.optional().or(['public', 'home', 'followers', 'specified', 'private']).get(params.visibility); if (visibilityErr) return rej('invalid visibility'); @@ -20,7 +17,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res const [visibleUserIds, visibleUserIdsErr] = $.arr($.type(ID)).optional().unique().min(1).get(params.visibleUserIds); if (visibleUserIdsErr) return rej('invalid visibleUserIds'); - let visibleUsers = []; + let visibleUsers: IUser[] = []; if (visibleUserIds !== undefined) { visibleUsers = await Promise.all(visibleUserIds.map(id => User.findOne({ _id: id @@ -132,7 +129,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res if (pollErr) return rej('invalid poll'); if (poll) { - (poll as any).choices = (poll as any).choices.map((choice, i) => ({ + (poll as any).choices = (poll as any).choices.map((choice: string, i: number) => ({ id: i, // IDを付与 text: choice.trim(), votes: 0 @@ -140,7 +137,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res } // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー - if (text === undefined && files === null && renote === null && poll === undefined) { + if ((text === undefined || text === null) && files === null && renote === null && poll === undefined) { return rej('text, mediaIds, renoteId or poll is required'); } diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts new file mode 100644 index 0000000000..70bcdbaab9 --- /dev/null +++ b/src/server/api/endpoints/notes/delete.ts @@ -0,0 +1,27 @@ +import $ from 'cafy'; import ID from '../../../../cafy-id'; +import Note from '../../../../models/note'; +import deleteNote from '../../../../services/note/delete'; +import { ILocalUser } from '../../../../models/user'; + +/** + * Delete a note + */ +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Get 'noteId' parameter + const [noteId, noteIdErr] = $.type(ID).get(params.noteId); + if (noteIdErr) return rej('invalid noteId param'); + + // Fetch note + const note = await Note.findOne({ + _id: noteId, + userId: user._id + }); + + if (note === null) { + return rej('note not found'); + } + + await deleteNote(user, note); + + res(); +}); diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts index 6832b52f75..23f7ac5f8d 100644 --- a/src/server/api/endpoints/notes/favorites/create.ts +++ b/src/server/api/endpoints/notes/favorites/create.ts @@ -1,14 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; +import { ILocalUser } from '../../../../../models/user'; /** * Favorite a note */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts index 07112dae15..7d2d2b4cb5 100644 --- a/src/server/api/endpoints/notes/favorites/delete.ts +++ b/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,14 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; +import { ILocalUser } from '../../../../../models/user'; /** * Unfavorite a note */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index d22a1763de..24ffdbcba7 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -1,15 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Get timeline of global */ -module.exports = async (params, user, app) => { +module.exports = async (params: any, user: ILocalUser) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; @@ -35,10 +33,14 @@ module.exports = async (params, user, app) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } + // Get 'mediaOnly' parameter + const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); + if (mediaOnlyErr) throw 'invalid mediaOnly param'; + // ミュートしているユーザーを取得 - const mutedUserIds = (await Mute.find({ + const mutedUserIds = user ? (await Mute.find({ muterId: user._id - })).map(m => m.muteeId); + })).map(m => m.muteeId) : null; //#region Construct query const sort = { @@ -46,17 +48,27 @@ module.exports = async (params, user, app) => { }; const query = { - // mute - userId: { + // public only + visibility: 'public' + } as any; + + if (mutedUserIds && mutedUserIds.length > 0) { + query.userId = { $nin: mutedUserIds - }, - '_reply.userId': { + }; + + query['_reply.userId'] = { $nin: mutedUserIds - }, - '_renote.userId': { + }; + + query['_renote.userId'] = { $nin: mutedUserIds - } - } as any; + }; + } + + if (mediaOnly) { + query.mediaIds = { $exists: true, $ne: [] }; + } if (sinceId) { sort._id = 1; diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index e7ebe5d960..48490638d2 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -1,15 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Get timeline of local */ -module.exports = async (params, user, app) => { +module.exports = async (params: any, user: ILocalUser) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; @@ -35,10 +33,14 @@ module.exports = async (params, user, app) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } + // Get 'mediaOnly' parameter + const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); + if (mediaOnlyErr) throw 'invalid mediaOnly param'; + // ミュートしているユーザーを取得 - const mutedUserIds = (await Mute.find({ + const mutedUserIds = user ? (await Mute.find({ muterId: user._id - })).map(m => m.muteeId); + })).map(m => m.muteeId) : null; //#region Construct query const sort = { @@ -46,21 +48,31 @@ module.exports = async (params, user, app) => { }; const query = { - // mute - userId: { - $nin: mutedUserIds - }, - '_reply.userId': { - $nin: mutedUserIds - }, - '_renote.userId': { - $nin: mutedUserIds - }, + // public only + visibility: 'public', // local '_user.host': null } as any; + if (mutedUserIds && mutedUserIds.length > 0) { + query.userId = { + $nin: mutedUserIds + }; + + query['_reply.userId'] = { + $nin: mutedUserIds + }; + + query['_renote.userId'] = { + $nin: mutedUserIds + }; + } + + if (mediaOnly) { + query.mediaIds = { $exists: true, $ne: [] }; + } + if (sinceId) { sort._id = 1; query._id = { diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 163a6b4866..45511603af 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -1,19 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note from '../../../../models/note'; import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Get mentions of myself - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'following' parameter const [following = false, followingError] = $.bool.optional().get(params.following); diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts index a272378d19..640140c3d1 100644 --- a/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,14 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import Vote from '../../../../../models/poll-vote'; import Note, { pack } from '../../../../../models/note'; +import { ILocalUser } from '../../../../../models/user'; /** * Get recommended polls */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index f8f4515308..72ac6bb202 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -1,6 +1,3 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import Vote from '../../../../../models/poll-vote'; import Note from '../../../../../models/note'; @@ -8,11 +5,12 @@ import Watching from '../../../../../models/note-watching'; import watch from '../../../../../services/note/watch'; import { publishNoteStream } from '../../../../../publishers/stream'; import notify from '../../../../../publishers/notify'; +import { ILocalUser } from '../../../../../models/user'; /** * Vote poll of a note */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); @@ -58,8 +56,8 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Send response res(); - const inc = {}; - inc[`poll.choices.${findWithAttr(note.poll.choices, 'id', choice)}.votes`] = 1; + const inc: any = {}; + inc[`poll.choices.${note.poll.choices.findIndex(c => c.id == choice)}.votes`] = 1; // Increment votes count await Note.update({ _id: note._id }, { @@ -100,12 +98,3 @@ module.exports = (params, user) => new Promise(async (res, rej) => { watch(user._id, note); } }); - -function findWithAttr(array, attr, value) { - for (let i = 0; i < array.length; i += 1) { - if (array[i][attr] === value) { - return i; - } - } - return -1; -} diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index 4ad952a7a1..d3b2d43432 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -1,18 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note from '../../../../models/note'; import Reaction, { pack } from '../../../../models/note-reaction'; +import { ILocalUser } from '../../../../models/user'; /** * Show reactions of a note - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index 21757cb427..33e457e308 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -1,15 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import Note from '../../../../../models/note'; import create from '../../../../../services/note/reaction/create'; import { validateReaction } from '../../../../../models/note-reaction'; +import { ILocalUser } from '../../../../../models/user'; /** * React to a note */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/reactions/delete.ts b/src/server/api/endpoints/notes/reactions/delete.ts index afb8629112..1f2d662511 100644 --- a/src/server/api/endpoints/notes/reactions/delete.ts +++ b/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,14 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import Reaction from '../../../../../models/note-reaction'; import Note from '../../../../../models/note'; +import { ILocalUser } from '../../../../../models/user'; /** * Unreact to a note */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); @@ -45,7 +43,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Send response res(); - const dec = {}; + const dec: any = {}; dec[`reactionCounts.${exist.reaction}`] = -1; // Decrement reactions count diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index 11d221d8f7..4aaf1d322b 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -1,17 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note, { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** - * Show a replies of a note - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} + * Get replies of a note */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); @@ -24,10 +18,6 @@ module.exports = (params, user) => new Promise(async (res, rej) => { const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); if (offsetErr) return rej('invalid offset param'); - // Get 'sort' parameter - const [sort = 'desc', sortError] = $.str.optional().or('desc asc').get(params.sort); - if (sortError) return rej('invalid sort param'); - // Lookup note const note = await Note.findOne({ _id: noteId @@ -37,17 +27,8 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('note not found'); } - // Issue query - const replies = await Note - .find({ replyId: note._id }, { - limit: limit, - skip: offset, - sort: { - _id: sort == 'asc' ? 1 : -1 - } - }); + const ids = (note._replyIds || []).slice(offset, offset + limit); // Serialize - res(await Promise.all(replies.map(async note => - await pack(note, user)))); + res(await Promise.all(ids.map(id => pack(id, user)))); }); diff --git a/src/server/api/endpoints/notes/reposts.ts b/src/server/api/endpoints/notes/reposts.ts index 3098211b61..ea3f174e1a 100644 --- a/src/server/api/endpoints/notes/reposts.ts +++ b/src/server/api/endpoints/notes/reposts.ts @@ -1,17 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note, { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Show a renotes of a note - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search_by_tag.ts index 9705dcfd6e..9be7cfffb6 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -1,25 +1,17 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -const escapeRegexp = require('escape-regexp'); import Note from '../../../../models/note'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; /** - * Search a note - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} + * Search notes by tag */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'text' parameter - const [text, textError] = $.str.optional().get(params.text); - if (textError) return rej('invalid text param'); +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { + // Get 'tag' parameter + const [tag, tagError] = $.str.get(params.tag); + if (tagError) return rej('invalid tag param'); // Get 'includeUserIds' parameter const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional().get(params.includeUserIds); @@ -77,7 +69,6 @@ module.exports = (params, me) => new Promise(async (res, rej) => { const [limit = 10, limitErr] = $.num.optional().range(1, 30).get(params.limit); if (limitErr) return rej('invalid limit param'); - let includeUsers = includeUserIds; if (includeUserUsernames != null) { const ids = (await Promise.all(includeUserUsernames.map(async (username) => { const _user = await User.findOne({ @@ -85,10 +76,10 @@ module.exports = (params, me) => new Promise(async (res, rej) => { }); return _user ? _user._id : null; }))).filter(id => id != null); - includeUsers = includeUsers.concat(ids); + + ids.forEach(id => includeUserIds.push(id)); } - let excludeUsers = excludeUserIds; if (excludeUserUsernames != null) { const ids = (await Promise.all(excludeUserUsernames.map(async (username) => { const _user = await User.findOne({ @@ -96,50 +87,17 @@ module.exports = (params, me) => new Promise(async (res, rej) => { }); return _user ? _user._id : null; }))).filter(id => id != null); - excludeUsers = excludeUsers.concat(ids); - } - - search(res, rej, me, text, includeUsers, excludeUsers, following, - mute, reply, renote, media, poll, sinceDate, untilDate, offset, limit); -}); -async function search( - res, rej, me, text, includeUserIds, excludeUserIds, following, - mute, reply, renote, media, poll, sinceDate, untilDate, offset, max) { + ids.forEach(id => excludeUserIds.push(id)); + } let q: any = { - $and: [] + $and: [{ + tagsLower: tag.toLowerCase() + }] }; - const push = x => q.$and.push(x); - - if (text) { - // 完全一致検索 - if (/"""(.+?)"""/.test(text)) { - const x = text.match(/"""(.+?)"""/)[1]; - push({ - text: x - }); - } else { - const tags = text.split(' ').filter(x => x[0] == '#'); - if (tags) { - push({ - $and: tags.map(x => ({ - tags: x - })) - }); - } - - push({ - $and: text.split(' ').map(x => ({ - // キーワードが-で始まる場合そのキーワードを除外する - text: x[0] == '-' ? { - $not: new RegExp(escapeRegexp(x.substr(1))) - } : new RegExp(escapeRegexp(x)) - })) - }); - } - } + const push = (x: any) => q.$and.push(x); if (includeUserIds && includeUserIds.length != 0) { push({ @@ -354,11 +312,10 @@ async function search( sort: { _id: -1 }, - limit: max, + limit: limit, skip: offset }); // Serialize - res(await Promise.all(notes.map(async note => - await pack(note, me)))); -} + res(await Promise.all(notes.map(note => pack(note, me)))); +}); diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index 78dc55a703..1ba7145996 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -1,17 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note, { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Show a note - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'noteId' parameter const [noteId, noteIdErr] = $.type(ID).get(params.noteId); if (noteIdErr) return rej('invalid noteId param'); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 9f32555649..18c0acd379 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -1,16 +1,14 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; import { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Get timeline of myself */ -module.exports = async (params, user, app) => { +module.exports = async (params: any, user: ILocalUser) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; @@ -44,6 +42,10 @@ module.exports = async (params, user, app) => { const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes); if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; + // Get 'mediaOnly' parameter + const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); + if (mediaOnlyErr) throw 'invalid mediaOnly param'; + const [followings, mutedUserIds] = await Promise.all([ // フォローを取得 // Fetch following @@ -137,6 +139,12 @@ module.exports = async (params, user, app) => { }); } + if (mediaOnly) { + query.$and.push({ + mediaIds: { $exists: true, $ne: [] } + }); + } + if (sinceId) { sort._id = 1; query._id = { diff --git a/src/server/api/endpoints/notes/trend.ts b/src/server/api/endpoints/notes/trend.ts index 4735bec51e..9c0a1bb112 100644 --- a/src/server/api/endpoints/notes/trend.ts +++ b/src/server/api/endpoints/notes/trend.ts @@ -1,18 +1,12 @@ -/** - * Module dependencies - */ const ms = require('ms'); import $ from 'cafy'; import Note, { pack } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; /** * Get trend notes - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 9f8397d679..8aa800b712 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,16 +1,14 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; import UserList from '../../../../models/user-list'; +import { ILocalUser } from '../../../../models/user'; /** * Get timeline of a user list */ -module.exports = async (params, user, app) => { +module.exports = async (params: any, user: ILocalUser) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) throw 'invalid limit param'; @@ -44,6 +42,10 @@ module.exports = async (params, user, app) => { const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes); if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; + // Get 'mediaOnly' parameter + const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly); + if (mediaOnlyErr) throw 'invalid mediaOnly param'; + // Get 'listId' parameter const [listId, listIdErr] = $.type(ID).get(params.listId); if (listIdErr) throw 'invalid listId param'; @@ -146,6 +148,12 @@ module.exports = async (params, user, app) => { }); } + if (mediaOnly) { + query.$and.push({ + mediaIds: { $exists: true, $ne: [] } + }); + } + if (sinceId) { sort._id = 1; query._id = { diff --git a/src/server/api/endpoints/notifications/get_unread_count.ts b/src/server/api/endpoints/notifications/get_unread_count.ts deleted file mode 100644 index 9766366ff1..0000000000 --- a/src/server/api/endpoints/notifications/get_unread_count.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Module dependencies - */ -import Notification from '../../../../models/notification'; -import Mute from '../../../../models/mute'; - -/** - * Get count of unread notifications - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - const mute = await Mute.find({ - muterId: user._id - }); - const mutedUserIds = mute.map(m => m.muteeId); - - const count = await Notification - .count({ - notifieeId: user._id, - notifierId: { - $nin: mutedUserIds - }, - isRead: false - }); - - res({ - count: count - }); -}); diff --git a/src/server/api/endpoints/notifications/mark_as_read_all.ts b/src/server/api/endpoints/notifications/mark_as_read_all.ts index dce3cb4663..faaaf65a2d 100644 --- a/src/server/api/endpoints/notifications/mark_as_read_all.ts +++ b/src/server/api/endpoints/notifications/mark_as_read_all.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import Notification from '../../../../models/notification'; import event from '../../../../publishers/stream'; +import User, { ILocalUser } from '../../../../models/user'; /** * Mark as read all notifications */ -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Update documents await Notification.update({ notifieeId: user._id, @@ -23,6 +21,13 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Response res(); + // Update flag + User.update({ _id: user._id }, { + $set: { + hasUnreadNotification: false + } + }); + // 全ての通知を読みましたよというイベントを発行 event(user._id, 'read_all_notifications'); }); diff --git a/src/server/api/endpoints/othello/match/cancel.ts b/src/server/api/endpoints/othello/match/cancel.ts deleted file mode 100644 index 562e691061..0000000000 --- a/src/server/api/endpoints/othello/match/cancel.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Matching from '../../../../../models/othello-matching'; - -module.exports = (params, user) => new Promise(async (res, rej) => { - await Matching.remove({ - parentId: user._id - }); - - res(); -}); diff --git a/src/server/api/endpoints/othello/games.ts b/src/server/api/endpoints/reversi/games.ts index 2320a34b04..1455f191f7 100644 --- a/src/server/api/endpoints/othello/games.ts +++ b/src/server/api/endpoints/reversi/games.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import OthelloGame, { pack } from '../../../../models/othello-game'; +import ReversiGame, { pack } from '../../../../models/reversi-game'; +import { ILocalUser } from '../../../../models/user'; -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'my' parameter const [my = false, myErr] = $.bool.optional().get(params.my); if (myErr) return rej('invalid my param'); @@ -50,7 +51,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { } // Fetch games - const games = await OthelloGame.find(q, { + const games = await ReversiGame.find(q, { sort, limit }); diff --git a/src/server/api/endpoints/othello/games/show.ts b/src/server/api/endpoints/reversi/games/show.ts index 6b2f5ce137..d70ee547a2 100644 --- a/src/server/api/endpoints/othello/games/show.ts +++ b/src/server/api/endpoints/reversi/games/show.ts @@ -1,19 +1,20 @@ import $ from 'cafy'; import ID from '../../../../../cafy-id'; -import OthelloGame, { pack } from '../../../../../models/othello-game'; -import Othello from '../../../../../othello/core'; +import ReversiGame, { pack } from '../../../../../models/reversi-game'; +import Reversi from '../../../../../reversi/core'; +import { ILocalUser } from '../../../../../models/user'; -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'gameId' parameter const [gameId, gameIdErr] = $.type(ID).get(params.gameId); if (gameIdErr) return rej('invalid gameId param'); - const game = await OthelloGame.findOne({ _id: gameId }); + const game = await ReversiGame.findOne({ _id: gameId }); if (game == null) { return rej('game not found'); } - const o = new Othello(game.settings.map, { + const o = new Reversi(game.settings.map, { isLlotheo: game.settings.isLlotheo, canPutEverywhere: game.settings.canPutEverywhere, loopedBoard: game.settings.loopedBoard diff --git a/src/server/api/endpoints/othello/invitations.ts b/src/server/api/endpoints/reversi/invitations.ts index 4761537614..d7727071ae 100644 --- a/src/server/api/endpoints/othello/invitations.ts +++ b/src/server/api/endpoints/reversi/invitations.ts @@ -1,6 +1,7 @@ -import Matching, { pack as packMatching } from '../../../../models/othello-matching'; +import Matching, { pack as packMatching } from '../../../../models/reversi-matching'; +import { ILocalUser } from '../../../../models/user'; -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Find session const invitations = await Matching.find({ childId: user._id diff --git a/src/server/api/endpoints/othello/match.ts b/src/server/api/endpoints/reversi/match.ts index e70e579755..907df7cf43 100644 --- a/src/server/api/endpoints/othello/match.ts +++ b/src/server/api/endpoints/reversi/match.ts @@ -1,11 +1,11 @@ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import Matching, { pack as packMatching } from '../../../../models/othello-matching'; -import OthelloGame, { pack as packGame } from '../../../../models/othello-game'; -import User from '../../../../models/user'; -import publishUserStream, { publishOthelloStream } from '../../../../publishers/stream'; -import { eighteight } from '../../../../othello/maps'; +import Matching, { pack as packMatching } from '../../../../models/reversi-matching'; +import ReversiGame, { pack as packGame } from '../../../../models/reversi-game'; +import User, { ILocalUser } from '../../../../models/user'; +import publishUserStream, { publishReversiStream } from '../../../../publishers/stream'; +import { eighteight } from '../../../../reversi/maps'; -module.exports = (params, user) => new Promise(async (res, rej) => { +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [childId, childIdErr] = $.type(ID).get(params.userId); if (childIdErr) return rej('invalid userId param'); @@ -28,7 +28,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { }); // Create game - const game = await OthelloGame.insert({ + const game = await ReversiGame.insert({ createdAt: new Date(), user1Id: exist.parentId, user2Id: user._id, @@ -47,14 +47,14 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Reponse res(await packGame(game, user)); - publishOthelloStream(exist.parentId, 'matched', await packGame(game, exist.parentId)); + publishReversiStream(exist.parentId, 'matched', await packGame(game, exist.parentId)); const other = await Matching.count({ childId: user._id }); if (other == 0) { - publishUserStream(user._id, 'othello_no_invites'); + publishUserStream(user._id, 'reversi_no_invites'); } } else { // Fetch child @@ -88,8 +88,8 @@ module.exports = (params, user) => new Promise(async (res, rej) => { const packed = await packMatching(matching, child); // 招待 - publishOthelloStream(child._id, 'invited', packed); + publishReversiStream(child._id, 'invited', packed); - publishUserStream(child._id, 'othello_invited', packed); + publishUserStream(child._id, 'reversi_invited', packed); } }); diff --git a/src/server/api/endpoints/reversi/match/cancel.ts b/src/server/api/endpoints/reversi/match/cancel.ts new file mode 100644 index 0000000000..1c9c799dbe --- /dev/null +++ b/src/server/api/endpoints/reversi/match/cancel.ts @@ -0,0 +1,10 @@ +import Matching from '../../../../../models/reversi-matching'; +import { ILocalUser } from '../../../../../models/user'; + +module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + await Matching.remove({ + parentId: user._id + }); + + res(); +}); diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts index d1e17651f2..74ab6ba945 100644 --- a/src/server/api/endpoints/stats.ts +++ b/src/server/api/endpoints/stats.ts @@ -1,26 +1,10 @@ -import Note from '../../../models/note'; -import User from '../../../models/user'; +import Meta from '../../../models/meta'; /** * Get the misskey's statistics */ -module.exports = params => new Promise(async (res, rej) => { - const notesCount = await Note.count(); +module.exports = () => new Promise(async (res, rej) => { + const meta = await Meta.findOne(); - const usersCount = await User.count(); - - const originalNotesCount = await Note.count({ - '_user.host': null - }); - - const originalUsersCount = await User.count({ - host: null - }); - - res({ - notesCount, - usersCount, - originalNotesCount, - originalUsersCount - }); + res(meta.stats); }); diff --git a/src/server/api/endpoints/sw/register.ts b/src/server/api/endpoints/sw/register.ts index b22a8b08ef..f04a77fa4d 100644 --- a/src/server/api/endpoints/sw/register.ts +++ b/src/server/api/endpoints/sw/register.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import Subscription from '../../../../models/sw-subscription'; +import { ILocalUser } from '../../../../models/user'; /** * subscribe service worker */ -module.exports = async (params, user, app) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'endpoint' parameter const [endpoint, endpointErr] = $.str.get(params.endpoint); if (endpointErr) return rej('invalid endpoint param'); diff --git a/src/server/api/endpoints/username/available.ts b/src/server/api/endpoints/username/available.ts index b11bec4e58..aad3adc514 100644 --- a/src/server/api/endpoints/username/available.ts +++ b/src/server/api/endpoints/username/available.ts @@ -1,17 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import User from '../../../../models/user'; import { validateUsername } from '../../../../models/user'; /** * Check available username - * - * @param {any} params - * @return {Promise<any>} */ -module.exports = async (params) => new Promise(async (res, rej) => { +module.exports = async (params: any) => new Promise(async (res, rej) => { // Get 'username' parameter const [username, usernameError] = $.str.pipe(validateUsername).get(params.username); if (usernameError) return rej('invalid username param'); diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index eb581cb7e6..b8df6f3ecf 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -1,13 +1,10 @@ -/** - * Module dependencies - */ import $ from 'cafy'; -import User, { pack } from '../../../models/user'; +import User, { pack, ILocalUser } from '../../../models/user'; /** * Lists all users */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 810cd7341b..53133ee969 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -1,8 +1,5 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; @@ -10,7 +7,7 @@ import { getFriendIds } from '../../common/get-friends'; /** * Get followers of a user */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 3373b9d632..2061200198 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -1,20 +1,13 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; /** * Get following users of a user - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/users/get_frequently_replied_users.ts b/src/server/api/endpoints/users/get_frequently_replied_users.ts index 64d737a06b..ba8779d334 100644 --- a/src/server/api/endpoints/users/get_frequently_replied_users.ts +++ b/src/server/api/endpoints/users/get_frequently_replied_users.ts @@ -1,11 +1,8 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note from '../../../../models/note'; -import User, { pack } from '../../../../models/user'; +import User, { pack, ILocalUser } from '../../../../models/user'; -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).get(params.userId); if (userIdErr) return rej('invalid userId param'); @@ -64,7 +61,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { } }); - const repliedUsers = {}; + const repliedUsers: any = {}; // Extract replies from recent notes replyTargetNotes.forEach(note => { diff --git a/src/server/api/endpoints/users/lists/create.ts b/src/server/api/endpoints/users/lists/create.ts index 100a78b872..cdd1a0d813 100644 --- a/src/server/api/endpoints/users/lists/create.ts +++ b/src/server/api/endpoints/users/lists/create.ts @@ -1,13 +1,11 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import UserList, { pack } from '../../../../../models/user-list'; +import { ILocalUser } from '../../../../../models/user'; /** * Create a user list */ -module.exports = async (params, user) => new Promise(async (res, rej) => { +module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { // Get 'title' parameter const [title, titleErr] = $.str.range(1, 100).get(params.title); if (titleErr) return rej('invalid title param'); diff --git a/src/server/api/endpoints/users/lists/list.ts b/src/server/api/endpoints/users/lists/list.ts index d19339a1f5..bf8391d863 100644 --- a/src/server/api/endpoints/users/lists/list.ts +++ b/src/server/api/endpoints/users/lists/list.ts @@ -1,9 +1,10 @@ import UserList, { pack } from '../../../../../models/user-list'; +import { ILocalUser } from '../../../../../models/user'; /** * Add a user to a user list */ -module.exports = async (params, me) => new Promise(async (res, rej) => { +module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Fetch lists const userLists = await UserList.find({ userId: me._id, diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts index da5a9a134c..d8a3b3c9aa 100644 --- a/src/server/api/endpoints/users/lists/push.ts +++ b/src/server/api/endpoints/users/lists/push.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import UserList from '../../../../../models/user-list'; -import User, { pack as packUser, isRemoteUser, getGhost } from '../../../../../models/user'; +import User, { pack as packUser, isRemoteUser, getGhost, ILocalUser } from '../../../../../models/user'; import { publishUserListStream } from '../../../../../publishers/stream'; import ap from '../../../../../remote/activitypub/renderer'; import renderFollow from '../../../../../remote/activitypub/renderer/follow'; @@ -9,7 +9,7 @@ import { deliver } from '../../../../../queue'; /** * Add a user to a user list */ -module.exports = async (params, me) => new Promise(async (res, rej) => { +module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'listId' parameter const [listId, listIdErr] = $.type(ID).get(params.listId); if (listIdErr) return rej('invalid listId param'); diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts index 16cb3382fd..e4ae239613 100644 --- a/src/server/api/endpoints/users/lists/show.ts +++ b/src/server/api/endpoints/users/lists/show.ts @@ -1,10 +1,11 @@ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import UserList, { pack } from '../../../../../models/user-list'; +import { ILocalUser } from '../../../../../models/user'; /** * Show a user list */ -module.exports = async (params, me) => new Promise(async (res, rej) => { +module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'listId' parameter const [listId, listIdErr] = $.type(ID).get(params.listId); if (listIdErr) return rej('invalid listId param'); diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 061c363d0f..222a8d950a 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -1,15 +1,12 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import getHostLower from '../../common/get-host-lower'; import Note, { pack } from '../../../../models/note'; -import User from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; /** * Get notes of a user */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'userId' parameter const [userId, userIdErr] = $.type(ID).optional().get(params.userId); if (userIdErr) return rej('invalid userId param'); diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 620ae17ca2..1d0d889f11 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -1,20 +1,13 @@ -/** - * Module dependencies - */ const ms = require('ms'); import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; +import User, { pack, ILocalUser } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; import Mute from '../../../../models/mute'; /** * Get recommended users - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); @@ -36,6 +29,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { _id: { $nin: followingIds.concat(mutedUserIds) }, + isLocked: false, $or: [{ lastUsedAt: { $gte: new Date(Date.now() - ms('7days')) diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index cfbdc337bf..e29c8d32f1 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -1,45 +1,25 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; -import config from '../../../../config'; +import User, { pack, ILocalUser } from '../../../../models/user'; const escapeRegexp = require('escape-regexp'); /** * Search a user - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'query' parameter const [query, queryError] = $.str.pipe(x => x != '').get(params.query); if (queryError) return rej('invalid query param'); - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); - // Get 'max' parameter const [max = 10, maxErr] = $.num.optional().range(1, 30).get(params.max); if (maxErr) return rej('invalid max param'); - // If Elasticsearch is available, search by $ - // If not, search by MongoDB - (config.elasticsearch.enable ? byElasticsearch : byNative) - (res, rej, me, query, offset, max); -}); - -// Search by MongoDB -async function byNative(res, rej, me, query, offset, max) { const escapedQuery = escapeRegexp(query); // Search users const users = await User .find({ + host: null, $or: [{ usernameLower: new RegExp(escapedQuery.replace('@', '').toLowerCase()) }, { @@ -50,49 +30,5 @@ async function byNative(res, rej, me, query, offset, max) { }); // Serialize - res(await Promise.all(users.map(async user => - await pack(user, me, { detail: true })))); -} - -// Search by Elasticsearch -async function byElasticsearch(res, rej, me, query, offset, max) { - const es = require('../../db/elasticsearch'); - - es.search({ - index: 'misskey', - type: 'user', - body: { - size: max, - from: offset, - query: { - simple_query_string: { - fields: ['username', 'name', 'bio'], - query: query, - default_operator: 'and' - } - } - } - }, async (error, response) => { - if (error) { - console.error(error); - return res(500); - } - - if (response.hits.total === 0) { - return res([]); - } - - const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id)); - - const users = await User - .find({ - _id: { - $in: hits - } - }); - - // Serialize - res(await Promise.all(users.map(async user => - await pack(user, me, { detail: true })))); - }); -} + res(await Promise.all(users.map(user => pack(user, me, { detail: true })))); +}); diff --git a/src/server/api/endpoints/users/search_by_username.ts b/src/server/api/endpoints/users/search_by_username.ts index 5927d00faf..937f9af589 100644 --- a/src/server/api/endpoints/users/search_by_username.ts +++ b/src/server/api/endpoints/users/search_by_username.ts @@ -1,13 +1,10 @@ -/** - * Module dependencies - */ import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; +import User, { pack, ILocalUser } from '../../../../models/user'; /** * Search a user by username */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { // Get 'query' parameter const [query, queryError] = $.str.get(params.query); if (queryError) return rej('invalid query param'); @@ -20,15 +17,27 @@ module.exports = (params, me) => new Promise(async (res, rej) => { const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit); if (limitErr) return rej('invalid limit param'); - const users = await User + let users = await User .find({ + host: null, usernameLower: new RegExp(query.toLowerCase()) }, { limit: limit, skip: offset }); + if (users.length < limit) { + const remoteUsers = await User + .find({ + host: { $ne: null }, + usernameLower: new RegExp(query.toLowerCase()) + }, { + limit: limit - users.length + }); + + users = users.concat(remoteUsers); + } + // Serialize - res(await Promise.all(users.map(async user => - await pack(user, me, { detail: true })))); + res(await Promise.all(users.map(user => pack(user, me, { detail: true })))); }); diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index b8c6ff25c4..bf7e2a2312 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -1,8 +1,5 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../../cafy-id'; -import User, { pack } from '../../../../models/user'; +import User, { pack, ILocalUser } from '../../../../models/user'; import resolveRemoteUser from '../../../../remote/resolve-user'; const cursorOption = { fields: { data: false } }; @@ -10,7 +7,7 @@ const cursorOption = { fields: { data: false } }; /** * Show user(s) */ -module.exports = (params, me) => new Promise(async (res, rej) => { +module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => { let user; // Get 'userId' parameter @@ -49,7 +46,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { return rej('failed to resolve remote user'); } } else { - const q = userId !== undefined + const q: any = userId !== undefined ? { _id: userId } : { usernameLower: username.toLowerCase(), host: null }; diff --git a/src/server/api/index.ts b/src/server/api/index.ts index 009c99acae..c39911c35f 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -6,6 +6,7 @@ import * as Koa from 'koa'; import * as Router from 'koa-router'; import * as multer from 'koa-multer'; import * as bodyParser from 'koa-bodyparser'; +const cors = require('@koa/cors'); import endpoints from './endpoints'; @@ -13,6 +14,11 @@ const handler = require('./api-handler').default; // Init app const app = new Koa(); + +app.use(cors({ + origin: '*' +})); + app.use(bodyParser({ // リクエストが multipart/form-data でない限りはJSONだと見なす detectJSON: ctx => !ctx.is('multipart/form-data') diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index cf51dec4d2..cb47d400b0 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -1,38 +1,16 @@ -import * as uuid from 'uuid'; import * as Koa from 'koa'; import * as bcrypt from 'bcryptjs'; import { generate as generateKeypair } from '../../../crypto_key'; -import recaptcha = require('recaptcha-promise'); +const recaptcha = require('recaptcha-promise'); import User, { IUser, validateUsername, validatePassword, pack } from '../../../models/user'; import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; +import Meta from '../../../models/meta'; recaptcha.init({ secret_key: config.recaptcha.secret_key }); -const home = { - left: [ - 'profile', - 'calendar', - 'activity', - 'rss', - 'trends', - 'photo-stream', - 'version' - ], - right: [ - 'broadcast', - 'notifications', - 'users', - 'polls', - 'server', - 'donation', - 'nav', - 'tips' - ] -}; - export default async (ctx: Koa.Context) => { // Verify recaptcha // ただしテスト時はこの機構は障害となるため無効にする @@ -82,28 +60,6 @@ export default async (ctx: Koa.Context) => { // Generate secret const secret = generateUserToken(); - //#region Construct home data - const homeData = []; - - home.left.forEach(widget => { - homeData.push({ - name: widget, - id: uuid(), - place: 'left', - data: {} - }); - }); - - home.right.forEach(widget => { - homeData.push({ - name: widget, - id: uuid(), - place: 'right', - data: {} - }); - }); - //#endregion - // Create account const account: IUser = await User.insert({ avatarId: null, @@ -135,12 +91,18 @@ export default async (ctx: Koa.Context) => { }, settings: { autoWatch: true - }, - clientSettings: { - home: homeData } }); + //#region Increment users count + Meta.update({}, { + $inc: { + 'stats.usersCount': 1, + 'stats.originalUsersCount': 1 + } + }, { upsert: true }); + //#endregion + // Response ctx.body = await pack(account); }; diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index cd9760a36d..9024740a96 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -11,7 +11,7 @@ const handler = new EventEmitter(); let bot: IUser; -const post = async text => { +const post = async (text: string) => { if (bot == null) { const account = await User.findOne({ usernameLower: config.github_bot.username.toLowerCase() @@ -25,7 +25,7 @@ const post = async text => { } } - createNote(bot, { text }); + createNote(bot, { text, visibility: 'home' }); }; // Init router @@ -90,7 +90,7 @@ handler.on('push', event => { case 'refs/heads/master': const pusher = event.pusher; const compare = event.compare; - const commits = event.commits; + const commits: any[] = event.commits; post([ `Pushed by **${pusher.name}** with ?[${commits.length} commit${commits.length > 1 ? 's' : ''}](${compare}):`, commits.reverse().map(commit => `・[?[${commit.id.substr(0, 7)}](${commit.url})] ${commit.message.split('\n')[0]}`).join('\n'), diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index 284ae7ee22..8c35509cce 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -49,7 +49,7 @@ router.get('/disconnect/twitter', async ctx => { ctx.body = `Twitterの連携を解除しました :v:`; // Publish i updated event - event(user._id, 'i_updated', await pack(user, user, { + event(user._id, 'meUpdated', await pack(user, user, { detail: true, includeSecrets: true })); @@ -174,7 +174,7 @@ if (config.twitter == null) { ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - event(user._id, 'i_updated', await pack(user, user, { + event(user._id, 'meUpdated', await pack(user, user, { detail: true, includeSecrets: true })); diff --git a/src/server/api/stream/home.ts b/src/server/api/stream/home.ts index 54fde2d776..d9b8f7fb96 100644 --- a/src/server/api/stream/home.ts +++ b/src/server/api/stream/home.ts @@ -4,7 +4,7 @@ import * as debug from 'debug'; import User, { IUser } from '../../../models/user'; import Mute from '../../../models/mute'; -import { pack as packNote } from '../../../models/note'; +import { pack as packNote, pack } from '../../../models/note'; import readNotification from '../common/read-notification'; import call from '../call'; import { IApp } from '../../../models/app'; @@ -48,6 +48,14 @@ export default async function( } //#endregion + // Renoteなら再pack + if (x.type == 'note' && x.body.renoteId != null) { + x.body.renote = await pack(x.body.renoteId, user, { + detail: true + }); + data = JSON.stringify(x); + } + connection.send(data); } catch (e) { connection.send(data); diff --git a/src/server/api/stream/local-timeline.ts b/src/server/api/stream/local-timeline.ts index a790ba878b..8f6a445be0 100644 --- a/src/server/api/stream/local-timeline.ts +++ b/src/server/api/stream/local-timeline.ts @@ -3,6 +3,7 @@ import * as redis from 'redis'; import { IUser } from '../../../models/user'; import Mute from '../../../models/mute'; +import { pack } from '../../../models/note'; export default async function( request: websocket.request, @@ -31,6 +32,13 @@ export default async function( } //#endregion + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await pack(note.renoteId, user, { + detail: true + }); + } + connection.send(JSON.stringify({ type: 'note', body: note diff --git a/src/server/api/stream/notes-stats.ts b/src/server/api/stream/notes-stats.ts new file mode 100644 index 0000000000..ab00620018 --- /dev/null +++ b/src/server/api/stream/notes-stats.ts @@ -0,0 +1,35 @@ +import * as websocket from 'websocket'; +import Xev from 'xev'; + +const ev = new Xev(); + +export default function(request: websocket.request, connection: websocket.connection): void { + const onStats = (stats: any) => { + connection.send(JSON.stringify({ + type: 'stats', + body: stats + })); + }; + + connection.on('message', async data => { + const msg = JSON.parse(data.utf8Data); + + switch (msg.type) { + case 'requestLog': + ev.once('notesStatsLog:' + msg.id, statsLog => { + connection.send(JSON.stringify({ + type: 'statsLog', + body: statsLog + })); + }); + ev.emit('requestNotesStatsLog', msg.id); + break; + } + }); + + ev.addListener('notesStats', onStats); + + connection.on('close', () => { + ev.removeListener('notesStats', onStats); + }); +} diff --git a/src/server/api/stream/requests.ts b/src/server/api/stream/requests.ts deleted file mode 100644 index d7bb5e6c5c..0000000000 --- a/src/server/api/stream/requests.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as websocket from 'websocket'; -import Xev from 'xev'; - -const ev = new Xev(); - -export default function(request: websocket.request, connection: websocket.connection): void { - const onRequest = request => { - connection.send(JSON.stringify({ - type: 'request', - body: request - })); - }; - - ev.addListener('request', onRequest); - - connection.on('close', () => { - ev.removeListener('request', onRequest); - }); -} diff --git a/src/server/api/stream/othello-game.ts b/src/server/api/stream/reversi-game.ts index 841e542610..ea8a9741d2 100644 --- a/src/server/api/stream/othello-game.ts +++ b/src/server/api/stream/reversi-game.ts @@ -1,10 +1,10 @@ import * as websocket from 'websocket'; import * as redis from 'redis'; import * as CRC32 from 'crc-32'; -import OthelloGame, { pack } from '../../../models/othello-game'; -import { publishOthelloGameStream } from '../../../publishers/stream'; -import Othello from '../../../othello/core'; -import * as maps from '../../../othello/maps'; +import ReversiGame, { pack } from '../../../models/reversi-game'; +import { publishReversiGameStream } from '../../../publishers/stream'; +import Reversi from '../../../reversi/core'; +import * as maps from '../../../reversi/maps'; import { ParsedUrlQuery } from 'querystring'; export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user?: any): void { @@ -12,7 +12,7 @@ export default function(request: websocket.request, connection: websocket.connec const gameId = q.game; // Subscribe game stream - subscriber.subscribe(`misskey:othello-game-stream:${gameId}`); + subscriber.subscribe(`misskey:reversi-game-stream:${gameId}`); subscriber.on('message', (_, data) => { connection.send(data); }); @@ -61,25 +61,25 @@ export default function(request: websocket.request, connection: websocket.connec } }); - async function updateSettings(settings) { - const game = await OthelloGame.findOne({ _id: gameId }); + async function updateSettings(settings: any) { + const game = await ReversiGame.findOne({ _id: gameId }); if (game.isStarted) return; if (!game.user1Id.equals(user._id) && !game.user2Id.equals(user._id)) return; if (game.user1Id.equals(user._id) && game.user1Accepted) return; if (game.user2Id.equals(user._id) && game.user2Accepted) return; - await OthelloGame.update({ _id: gameId }, { + await ReversiGame.update({ _id: gameId }, { $set: { settings } }); - publishOthelloGameStream(gameId, 'update-settings', settings); + publishReversiGameStream(gameId, 'update-settings', settings); } - async function initForm(form) { - const game = await OthelloGame.findOne({ _id: gameId }); + async function initForm(form: any) { + const game = await ReversiGame.findOne({ _id: gameId }); if (game.isStarted) return; if (!game.user1Id.equals(user._id) && !game.user2Id.equals(user._id)) return; @@ -90,25 +90,25 @@ export default function(request: websocket.request, connection: websocket.connec form2: form }; - await OthelloGame.update({ _id: gameId }, { + await ReversiGame.update({ _id: gameId }, { $set: set }); - publishOthelloGameStream(gameId, 'init-form', { + publishReversiGameStream(gameId, 'init-form', { userId: user._id, form }); } - async function updateForm(id, value) { - const game = await OthelloGame.findOne({ _id: gameId }); + async function updateForm(id: string, value: any) { + const game = await ReversiGame.findOne({ _id: gameId }); if (game.isStarted) return; if (!game.user1Id.equals(user._id) && !game.user2Id.equals(user._id)) return; const form = game.user1Id.equals(user._id) ? game.form2 : game.form1; - const item = form.find(i => i.id == id); + const item = form.find((i: any) => i.id == id); if (item == null) return; @@ -120,53 +120,53 @@ export default function(request: websocket.request, connection: websocket.connec form1: form }; - await OthelloGame.update({ _id: gameId }, { + await ReversiGame.update({ _id: gameId }, { $set: set }); - publishOthelloGameStream(gameId, 'update-form', { + publishReversiGameStream(gameId, 'update-form', { userId: user._id, id, value }); } - async function message(message) { + async function message(message: any) { message.id = Math.random(); - publishOthelloGameStream(gameId, 'message', { + publishReversiGameStream(gameId, 'message', { userId: user._id, message }); } async function accept(accept: boolean) { - const game = await OthelloGame.findOne({ _id: gameId }); + const game = await ReversiGame.findOne({ _id: gameId }); if (game.isStarted) return; let bothAccepted = false; if (game.user1Id.equals(user._id)) { - await OthelloGame.update({ _id: gameId }, { + await ReversiGame.update({ _id: gameId }, { $set: { user1Accepted: accept } }); - publishOthelloGameStream(gameId, 'change-accepts', { + publishReversiGameStream(gameId, 'change-accepts', { user1: accept, user2: game.user2Accepted }); if (accept && game.user2Accepted) bothAccepted = true; } else if (game.user2Id.equals(user._id)) { - await OthelloGame.update({ _id: gameId }, { + await ReversiGame.update({ _id: gameId }, { $set: { user2Accepted: accept } }); - publishOthelloGameStream(gameId, 'change-accepts', { + publishReversiGameStream(gameId, 'change-accepts', { user1: game.user1Accepted, user2: accept }); @@ -179,7 +179,7 @@ export default function(request: websocket.request, connection: websocket.connec if (bothAccepted) { // 3秒後、まだacceptされていたらゲーム開始 setTimeout(async () => { - const freshGame = await OthelloGame.findOne({ _id: gameId }); + const freshGame = await ReversiGame.findOne({ _id: gameId }); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; @@ -198,7 +198,7 @@ export default function(request: websocket.request, connection: websocket.connec const map = freshGame.settings.map != null ? freshGame.settings.map : getRandomMap(); - await OthelloGame.update({ _id: gameId }, { + await ReversiGame.update({ _id: gameId }, { $set: { startedAt: new Date(), isStarted: true, @@ -208,7 +208,7 @@ export default function(request: websocket.request, connection: websocket.connec }); //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 - const o = new Othello(map, { + const o = new Reversi(map, { isLlotheo: freshGame.settings.isLlotheo, canPutEverywhere: freshGame.settings.canPutEverywhere, loopedBoard: freshGame.settings.loopedBoard @@ -224,7 +224,7 @@ export default function(request: websocket.request, connection: websocket.connec winner = null; } - await OthelloGame.update({ + await ReversiGame.update({ _id: gameId }, { $set: { @@ -233,27 +233,27 @@ export default function(request: websocket.request, connection: websocket.connec } }); - publishOthelloGameStream(gameId, 'ended', { + publishReversiGameStream(gameId, 'ended', { winnerId: winner, game: await pack(gameId, user) }); } //#endregion - publishOthelloGameStream(gameId, 'started', await pack(gameId, user)); + publishReversiGameStream(gameId, 'started', await pack(gameId, user)); }, 3000); } } // 石を打つ - async function set(pos) { - const game = await OthelloGame.findOne({ _id: gameId }); + async function set(pos: number) { + const game = await ReversiGame.findOne({ _id: gameId }); if (!game.isStarted) return; if (game.isEnded) return; if (!game.user1Id.equals(user._id) && !game.user2Id.equals(user._id)) return; - const o = new Othello(game.settings.map, { + const o = new Reversi(game.settings.map, { isLlotheo: game.settings.isLlotheo, canPutEverywhere: game.settings.canPutEverywhere, loopedBoard: game.settings.loopedBoard @@ -290,7 +290,7 @@ export default function(request: websocket.request, connection: websocket.connec const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()); - await OthelloGame.update({ + await ReversiGame.update({ _id: gameId }, { $set: { @@ -303,20 +303,20 @@ export default function(request: websocket.request, connection: websocket.connec } }); - publishOthelloGameStream(gameId, 'set', Object.assign(log, { + publishReversiGameStream(gameId, 'set', Object.assign(log, { next: o.turn })); if (o.isEnded) { - publishOthelloGameStream(gameId, 'ended', { + publishReversiGameStream(gameId, 'ended', { winnerId: winner, game: await pack(gameId, user) }); } } - async function check(crc32) { - const game = await OthelloGame.findOne({ _id: gameId }); + async function check(crc32: string) { + const game = await ReversiGame.findOne({ _id: gameId }); if (!game.isStarted) return; diff --git a/src/server/api/stream/othello.ts b/src/server/api/stream/reversi.ts index fa62b05836..35c6167364 100644 --- a/src/server/api/stream/othello.ts +++ b/src/server/api/stream/reversi.ts @@ -1,12 +1,12 @@ import * as mongo from 'mongodb'; import * as websocket from 'websocket'; import * as redis from 'redis'; -import Matching, { pack } from '../../../models/othello-matching'; +import Matching, { pack } from '../../../models/reversi-matching'; import publishUserStream from '../../../publishers/stream'; export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { - // Subscribe othello stream - subscriber.subscribe(`misskey:othello-stream:${user._id}`); + // Subscribe reversi stream + subscriber.subscribe(`misskey:reversi-stream:${user._id}`); subscriber.on('message', (_, data) => { connection.send(data); }); @@ -22,7 +22,7 @@ export default function(request: websocket.request, connection: websocket.connec childId: new mongo.ObjectID(msg.id) }); if (matching == null) return; - publishUserStream(matching.childId, 'othello_invited', await pack(matching, matching.childId)); + publishUserStream(matching.childId, 'reversi_invited', await pack(matching, matching.childId)); break; } }); diff --git a/src/server/api/stream/server-stats.ts b/src/server/api/stream/server-stats.ts new file mode 100644 index 0000000000..2a058de6c3 --- /dev/null +++ b/src/server/api/stream/server-stats.ts @@ -0,0 +1,35 @@ +import * as websocket from 'websocket'; +import Xev from 'xev'; + +const ev = new Xev(); + +export default function(request: websocket.request, connection: websocket.connection): void { + const onStats = (stats: any) => { + connection.send(JSON.stringify({ + type: 'stats', + body: stats + })); + }; + + connection.on('message', async data => { + const msg = JSON.parse(data.utf8Data); + + switch (msg.type) { + case 'requestLog': + ev.once('serverStatsLog:' + msg.id, statsLog => { + connection.send(JSON.stringify({ + type: 'statsLog', + body: statsLog + })); + }); + ev.emit('requestServerStatsLog', msg.id); + break; + } + }); + + ev.addListener('serverStats', onStats); + + connection.on('close', () => { + ev.removeListener('serverStats', onStats); + }); +} diff --git a/src/server/api/stream/server.ts b/src/server/api/stream/server.ts deleted file mode 100644 index 4ca2ad1b10..0000000000 --- a/src/server/api/stream/server.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as websocket from 'websocket'; -import Xev from 'xev'; - -const ev = new Xev(); - -export default function(request: websocket.request, connection: websocket.connection): void { - const onStats = stats => { - connection.send(JSON.stringify({ - type: 'stats', - body: stats - })); - }; - - ev.addListener('stats', onStats); - - connection.on('close', () => { - ev.removeListener('stats', onStats); - }); -} diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts index 6825b6336a..81adff0b58 100644 --- a/src/server/api/streaming.ts +++ b/src/server/api/streaming.ts @@ -10,10 +10,10 @@ import userListStream from './stream/user-list'; import driveStream from './stream/drive'; import messagingStream from './stream/messaging'; import messagingIndexStream from './stream/messaging-index'; -import othelloGameStream from './stream/othello-game'; -import othelloStream from './stream/othello'; -import serverStream from './stream/server'; -import requestsStream from './stream/requests'; +import reversiGameStream from './stream/reversi-game'; +import reversiStream from './stream/reversi'; +import serverStatsStream from './stream/server-stats'; +import notesStatsStream from './stream/notes-stats'; import { ParsedUrlQuery } from 'querystring'; import authenticate from './authenticate'; @@ -28,13 +28,13 @@ module.exports = (server: http.Server) => { ws.on('request', async (request) => { const connection = request.accept(); - if (request.resourceURL.pathname === '/server') { - serverStream(request, connection); + if (request.resourceURL.pathname === '/server-stats') { + serverStatsStream(request, connection); return; } - if (request.resourceURL.pathname === '/requests') { - requestsStream(request, connection); + if (request.resourceURL.pathname === '/notes-stats') { + notesStatsStream(request, connection); return; } @@ -50,8 +50,8 @@ module.exports = (server: http.Server) => { const q = request.resourceURL.query as ParsedUrlQuery; const [user, app] = await authenticate(q.i as string); - if (request.resourceURL.pathname === '/othello-game') { - othelloGameStream(request, connection, subscriber, user); + if (request.resourceURL.pathname === '/reversi-game') { + reversiGameStream(request, connection, subscriber, user); return; } @@ -69,7 +69,7 @@ module.exports = (server: http.Server) => { request.resourceURL.pathname === '/drive' ? driveStream : request.resourceURL.pathname === '/messaging' ? messagingStream : request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream : - request.resourceURL.pathname === '/othello' ? othelloStream : + request.resourceURL.pathname === '/reversi' ? reversiStream : null; if (channel !== null) { |