From f7069dcd18d72b52408a6bd80ad8f14492163e19 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Wed, 31 Oct 2018 11:16:13 +0900 Subject: [PATCH] =?UTF-8?q?=E8=89=AF=E3=81=84=E6=84=9F=E3=81=98=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja-JP.yml | 13 +-- .../app/common/views/components/index.ts | 2 + .../views/components/mute-and-block.vue | 56 +++++++++++++ .../views/components/settings.blocking.vue | 31 ------- .../views/components/settings.mute.vue | 31 ------- .../app/desktop/views/components/settings.vue | 23 +----- .../app/mobile/views/pages/settings.vue | 2 + src/models/blocking.ts | 41 ++++++++++ src/models/mute.ts | 41 ++++++++++ src/server/api/endpoints/blocking/list.ts | 67 ++++++++------- src/server/api/endpoints/mute/list.ts | 82 ++++++++----------- 11 files changed, 225 insertions(+), 164 deletions(-) create mode 100644 src/client/app/common/views/components/mute-and-block.vue delete mode 100644 src/client/app/desktop/views/components/settings.blocking.vue delete mode 100644 src/client/app/desktop/views/components/settings.mute.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 69fde53f54..bb3a151add 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -832,7 +832,7 @@ desktop/views/components/settings.vue: profile: "プロフィール" notification: "通知" apps: "アプリ" - mute: "ミュート" + mute-and-block: "ミュート/ブロック" blocking: "ブロック" security: "セキュリティ" signin: "サインイン履歴" @@ -974,11 +974,12 @@ common/views/components/drive-settings.vue: in-use: "使用中" stats: "統計" -desktop/views/components/settings.mute.vue: - no-users: "ミュートしているユーザーはいません" - -desktop/views/components/settings.blocking.vue: - no-users: "ブロックしているユーザーはいません" +common/views/components/mute-and-block.vue: + mute-and-block: "ミュートとブロック" + mute: "ミュート" + block: "ブロック" + no-muted-users: "ミュートしているユーザーはいません" + no-blocked-users: "ブロックしているユーザーはいません" desktop/views/components/settings.password.vue: reset: "パスワードを変更する" diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index 33216e459d..2f2217f8d2 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -1,5 +1,6 @@ import Vue from 'vue'; +import muteAndBlock from './mute-and-block.vue'; import error from './error.vue'; import apiSettings from './api-settings.vue'; import driveSettings from './drive-settings.vue'; @@ -50,6 +51,7 @@ import uiInfo from './ui/info.vue'; import formButton from './ui/form/button.vue'; import formRadio from './ui/form/radio.vue'; +Vue.component('mk-mute-and-block', muteAndBlock); Vue.component('mk-error', error); Vue.component('mk-api-settings', apiSettings); Vue.component('mk-drive-settings', driveSettings); diff --git a/src/client/app/common/views/components/mute-and-block.vue b/src/client/app/common/views/components/mute-and-block.vue new file mode 100644 index 0000000000..b244f975bb --- /dev/null +++ b/src/client/app/common/views/components/mute-and-block.vue @@ -0,0 +1,56 @@ +<template> +<ui-card> + <div slot="title">%fa:ban% %i18n:@mute-and-block%</div> + + <section> + <header>%i18n:@mute%</header> + <ui-info v-if="!muteFetching && mute.length == 0"> + <p>%i18n:@no-muted-users%</p> + </ui-info> + <div class="users" v-if="mute.length != 0"> + <div v-for="user in mute" :key="user.id"> + <p><b>{{ user | userName }}</b> @{{ user | acct }}</p> + </div> + </div> + </section> + + <section> + <header>%i18n:@block%</header> + <ui-info v-if="!blockFetching && block.length == 0"> + <p>%i18n:@no-blocked-users%</p> + </ui-info> + <div class="users" v-if="block.length != 0"> + <div v-for="user in block" :key="user.id"> + <p><b>{{ user | userName }}</b> @{{ user | acct }}</p> + </div> + </div> + </section> +</ui-card> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + data() { + return { + muteFetching: true, + blockFetching: true, + mute: [], + block: [] + }; + }, + + mounted() { + (this as any).api('mute/list').then(mute => { + this.mute = mute.map(x => x.mutee); + this.muteFetching = false; + }); + + (this as any).api('blocking/list').then(blocking => { + this.block = blocking.map(x => x.blockee); + this.blockFetching = false; + }); + } +}); +</script> diff --git a/src/client/app/desktop/views/components/settings.blocking.vue b/src/client/app/desktop/views/components/settings.blocking.vue deleted file mode 100644 index 43e4bf9209..0000000000 --- a/src/client/app/desktop/views/components/settings.blocking.vue +++ /dev/null @@ -1,31 +0,0 @@ -<template> -<div> - <div class="none ui info" v-if="!fetching && users.length == 0"> - <p>%fa:info-circle%%i18n:@no-users%</p> - </div> - <div class="users" v-if="users.length != 0"> - <div v-for="user in users" :key="user.id"> - <p><b>{{ user | userName }}</b> @{{ user | acct }}</p> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import Vue from 'vue'; - -export default Vue.extend({ - data() { - return { - fetching: true, - users: [] - }; - }, - mounted() { - (this as any).api('blocking/list').then(x => { - this.users = x.users; - this.fetching = false; - }); - } -}); -</script> diff --git a/src/client/app/desktop/views/components/settings.mute.vue b/src/client/app/desktop/views/components/settings.mute.vue deleted file mode 100644 index 792afa923f..0000000000 --- a/src/client/app/desktop/views/components/settings.mute.vue +++ /dev/null @@ -1,31 +0,0 @@ -<template> -<div> - <div class="none ui info" v-if="!fetching && users.length == 0"> - <p>%fa:info-circle%%i18n:@no-users%</p> - </div> - <div class="users" v-if="users.length != 0"> - <div v-for="user in users" :key="user.id"> - <p><b>{{ user | userName }}</b> @{{ user | acct }}</p> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import Vue from 'vue'; - -export default Vue.extend({ - data() { - return { - fetching: true, - users: [] - }; - }, - mounted() { - (this as any).api('mute/list').then(x => { - this.users = x.users; - this.fetching = false; - }); - } -}); -</script> diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue index e77320bef3..74cedf9ecc 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -7,8 +7,7 @@ <p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'">%fa:R bell .fw%%i18n:@notification%</p> <p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'">%fa:cloud .fw%%i18n:common.drive%</p> <p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'">%fa:hashtag .fw%%i18n:@tags%</p> - <p :class="{ active: page == 'mute' }" @mousedown="page = 'mute'">%fa:ban .fw%%i18n:@mute%</p> - <p :class="{ active: page == 'blocking' }" @mousedown="page = 'blocking'">%fa:ban .fw%%i18n:@blocking%</p> + <p :class="{ active: page == 'muteAndBlock' }" @mousedown="page = 'muteAndBlock'">%fa:ban .fw%%i18n:@mute-and-block%</p> <p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'">%fa:puzzle-piece .fw%%i18n:@apps%</p> <p :class="{ active: page == 'security' }" @mousedown="page = 'security'">%fa:unlock-alt .fw%%i18n:@security%</p> <p :class="{ active: page == 'api' }" @mousedown="page = 'api'">%fa:key .fw%API</p> @@ -201,19 +200,9 @@ </section> </ui-card> - <ui-card class="mute" v-show="page == 'mute'"> - <div slot="title">%fa:ban% %i18n:@mute%</div> - <section> - <x-mute/> - </section> - </ui-card> - - <ui-card class="blocking" v-show="page == 'blocking'"> - <div slot="title">%fa:ban% %i18n:@blocking%</div> - <section> - <x-blocking/> - </section> - </ui-card> + <div class="muteAndBlock" v-show="page == 'muteAndBlock'"> + <mk-mute-and-block/> + </div> <ui-card class="apps" v-show="page == 'apps'"> <div slot="title">%fa:puzzle-piece% %i18n:@apps%</div> @@ -297,8 +286,6 @@ <script lang="ts"> import Vue from 'vue'; -import XMute from './settings.mute.vue'; -import XBlocking from './settings.blocking.vue'; import XPassword from './settings.password.vue'; import X2fa from './settings.2fa.vue'; import XApps from './settings.apps.vue'; @@ -309,8 +296,6 @@ import checkForUpdate from '../../../common/scripts/check-for-update'; export default Vue.extend({ components: { - XMute, - XBlocking, XPassword, X2fa, XApps, diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue index b96ed59311..a77f5d4c3a 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -85,6 +85,8 @@ <mk-drive-settings/> + <mk-mute-and-block/> + <ui-card> <div slot="title">%fa:volume-up% %i18n:@sound%</div> diff --git a/src/models/blocking.ts b/src/models/blocking.ts index 2974b53554..3eace3989a 100644 --- a/src/models/blocking.ts +++ b/src/models/blocking.ts @@ -1,7 +1,12 @@ import * as mongo from 'mongodb'; import db from '../db/mongodb'; +import isObjectId from '../misc/is-objectid'; +const deepcopy = require('deepcopy'); +import { pack as packUser } from './user'; const Blocking = db.get<IBlocking>('blocking'); +Blocking.createIndex('blockerId'); +Blocking.createIndex('blockeeId'); Blocking.createIndex(['blockerId', 'blockeeId'], { unique: true }); export default Blocking; @@ -11,3 +16,39 @@ export type IBlocking = { blockeeId: mongo.ObjectID; blockerId: mongo.ObjectID; }; + +export const packMany = async ( + blockings: (string | mongo.ObjectID | IBlocking)[], + me?: string | mongo.ObjectID | IUser +) => { + return (await Promise.all(blockings.map(x => pack(x, me)))).filter(x => x != null); +}; + +export const pack = ( + blocking: any, + me?: any +) => new Promise<any>(async (resolve, reject) => { + let _blocking: any; + + // Populate the blocking if 'blocking' is ID + if (isObjectId(blocking)) { + _blocking = await Blocking.findOne({ + _id: blocking + }); + } else if (typeof blocking === 'string') { + _blocking = await Blocking.findOne({ + _id: new mongo.ObjectID(blocking) + }); + } else { + _blocking = deepcopy(blocking); + } + + // Rename _id to id + _blocking.id = _blocking._id; + delete _blocking._id; + + // Populate blockee + _blocking.blockee = await packUser(_blocking.blockeeId, me); + + resolve(_blocking); +}); diff --git a/src/models/mute.ts b/src/models/mute.ts index 4bad3d3d31..58536d4b9c 100644 --- a/src/models/mute.ts +++ b/src/models/mute.ts @@ -1,7 +1,12 @@ import * as mongo from 'mongodb'; import db from '../db/mongodb'; +import isObjectId from '../misc/is-objectid'; +const deepcopy = require('deepcopy'); +import { pack as packUser } from './user'; const Mute = db.get<IMute>('mute'); +Mute.createIndex('muterId'); +Mute.createIndex('muteeId'); Mute.createIndex(['muterId', 'muteeId'], { unique: true }); export default Mute; @@ -11,3 +16,39 @@ export interface IMute { muterId: mongo.ObjectID; muteeId: mongo.ObjectID; } + +export const packMany = async ( + mutes: (string | mongo.ObjectID | IMute)[], + me?: string | mongo.ObjectID | IUser +) => { + return (await Promise.all(mutes.map(x => pack(x, me)))).filter(x => x != null); +}; + +export const pack = ( + mute: any, + me?: any +) => new Promise<any>(async (resolve, reject) => { + let _mute: any; + + // Populate the mute if 'mute' is ID + if (isObjectId(mute)) { + _mute = await Mute.findOne({ + _id: mute + }); + } else if (typeof mute === 'string') { + _mute = await Mute.findOne({ + _id: new mongo.ObjectID(mute) + }); + } else { + _mute = deepcopy(mute); + } + + // Rename _id to id + _mute.id = _mute._id; + delete _mute._id; + + // Populate mutee + _mute.mutee = await packUser(_mute.muteeId, me); + + resolve(_mute); +}); diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts index ba1c77d2c7..a0bef38b58 100644 --- a/src/server/api/endpoints/blocking/list.ts +++ b/src/server/api/endpoints/blocking/list.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Blocking from '../../../../models/blocking'; -import { pack, ILocalUser } from '../../../../models/user'; +import Blocking, { packMany } from '../../../../models/blocking'; +import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,50 +11,54 @@ export const meta = { requireCredential: true, - kind: 'following-read' + kind: 'following-read', + + params: { + limit: $.num.optional.range(1, 100).note({ + default: 30 + }), + + sinceId: $.type(ID).optional.note({ + }), + + untilId: $.type(ID).optional.note({ + }), + } }; export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 30, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'cursor' parameter - const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor); - if (cursorErr) return rej('invalid cursor param'); + // Check if both of sinceId and untilId is specified + if (ps.sinceId && ps.untilId) { + return rej('cannot set sinceId and untilId'); + } - // Construct query const query = { blockerId: me._id } as any; - // カーソルが指定されている場合 - if (cursor) { + const sort = { + _id: -1 + }; + + if (ps.sinceId) { + sort._id = 1; query._id = { - $lt: cursor + $gt: ps.sinceId + }; + } else if (ps.untilId) { + query._id = { + $lt: ps.untilId }; } - // Get blockings const blockings = await Blocking .find(query, { - limit: limit + 1, - sort: { _id: -1 } + limit: ps.limit, + sort: sort }); - // 「次のページ」があるかどうか - const inStock = blockings.length === limit + 1; - if (inStock) { - blockings.pop(); - } - - // Serialize - const users = await Promise.all(blockings.map(async m => - await pack(m.blockeeId, me, { detail: true }))); - - // Response - res({ - users: users, - next: inStock ? blockings[blockings.length - 1]._id : null, - }); + res(await packMany(blockings, me)); }); diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index 387b2396f5..4653877621 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Mute from '../../../../models/mute'; -import { pack, ILocalUser } from '../../../../models/user'; -import { getFriendIds } from '../../common/get-friends'; +import Mute, { packMany } from '../../../../models/mute'; +import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -11,64 +11,54 @@ export const meta = { requireCredential: true, - kind: 'account/read' + kind: 'account/read', + + params: { + limit: $.num.optional.range(1, 100).note({ + default: 30 + }), + + sinceId: $.type(ID).optional.note({ + }), + + untilId: $.type(ID).optional.note({ + }), + } }; export default (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'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'limit' parameter - const [limit = 30, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); + // Check if both of sinceId and untilId is specified + if (ps.sinceId && ps.untilId) { + return rej('cannot set sinceId and untilId'); + } - // Get 'cursor' parameter - const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor); - if (cursorErr) return rej('invalid cursor param'); - - // Construct query const query = { - muterId: me._id, - deletedAt: { $exists: false } + muterId: me._id } as any; - if (iknow) { - // Get my friends - const myFriends = await getFriendIds(me._id); + const sort = { + _id: -1 + }; - query.muteeId = { - $in: myFriends - }; - } - - // カーソルが指定されている場合 - if (cursor) { + if (ps.sinceId) { + sort._id = 1; query._id = { - $lt: cursor + $gt: ps.sinceId + }; + } else if (ps.untilId) { + query._id = { + $lt: ps.untilId }; } - // Get mutes const mutes = await Mute .find(query, { - limit: limit + 1, - sort: { _id: -1 } + limit: ps.limit, + sort: sort }); - // 「次のページ」があるかどうか - const inStock = mutes.length === limit + 1; - if (inStock) { - mutes.pop(); - } - - // Serialize - const users = await Promise.all(mutes.map(async m => - await pack(m.muteeId, me, { detail: true }))); - - // Response - res({ - users: users, - next: inStock ? mutes[mutes.length - 1]._id : null, - }); + res(await packMany(mutes, me)); });