diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2018-11-15 04:15:42 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2018-11-15 04:15:42 +0900 |
| commit | 56d571c0f0f525263ea6257a5d5a2e7a9085e203 (patch) | |
| tree | 69c40b1012f8c632e6601c50be9c7e2fb1a12e5c /src | |
| parent | [Client] Add missing icon (diff) | |
| download | sharkey-56d571c0f0f525263ea6257a5d5a2e7a9085e203.tar.gz sharkey-56d571c0f0f525263ea6257a5d5a2e7a9085e203.tar.bz2 sharkey-56d571c0f0f525263ea6257a5d5a2e7a9085e203.zip | |
Moderator system
Closes #2357
Diffstat (limited to 'src')
22 files changed, 184 insertions, 17 deletions
diff --git a/src/client/app/admin/views/index.vue b/src/client/app/admin/views/index.vue index a5ffb2098e..116d794b91 100644 --- a/src/client/app/admin/views/index.vue +++ b/src/client/app/admin/views/index.vue @@ -20,6 +20,7 @@ <ul> <li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</li> <li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li> + <li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li> <li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li> <li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li> <li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li> @@ -38,6 +39,7 @@ <main> <div v-if="page == 'dashboard'"><x-dashboard/></div> <div v-if="page == 'instance'"><x-instance/></div> + <div v-if="page == 'moderators'"><x-moderators/></div> <div v-if="page == 'users'"><x-users/></div> <div v-if="page == 'emoji'"><x-emoji/></div> <div v-if="page == 'announcements'"><x-announcements/></div> @@ -54,11 +56,12 @@ import i18n from '../../i18n'; import { version } from '../../config'; import XDashboard from "./dashboard.vue"; import XInstance from "./instance.vue"; +import XModerators from "./moderators.vue"; import XEmoji from "./emoji.vue"; import XAnnouncements from "./announcements.vue"; import XHashtags from "./hashtags.vue"; import XUsers from "./users.vue"; -import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; +import { faHeadset, faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import { faGrin } from '@fortawesome/free-regular-svg-icons'; // Detect the user agent @@ -70,6 +73,7 @@ export default Vue.extend({ components: { XDashboard, XInstance, + XModerators, XEmoji, XAnnouncements, XHashtags, @@ -85,7 +89,8 @@ export default Vue.extend({ isMobile, navOpend: !isMobile, faGrin, - faArrowLeft + faArrowLeft, + faHeadset }; }, methods: { diff --git a/src/client/app/admin/views/moderators.vue b/src/client/app/admin/views/moderators.vue new file mode 100644 index 0000000000..ebf20c12fd --- /dev/null +++ b/src/client/app/admin/views/moderators.vue @@ -0,0 +1,61 @@ +<template> +<div class="jnhmugbb"> + <ui-card> + <div slot="title"><fa icon="plus"/> {{ $t('add-moderator.title') }}</div> + <section class="fit-top"> + <ui-input v-model="username" type="text"> + <span slot="prefix">@</span> + </ui-input> + <ui-button @click="add" :disabled="adding">{{ $t('add-moderator.add') }}</ui-button> + </section> + </ui-card> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import i18n from '../../i18n'; +import parseAcct from "../../../../misc/acct/parse"; + +export default Vue.extend({ + i18n: i18n('admin/views/moderators.vue'), + + data() { + return { + username: '', + adding: false + }; + }, + + methods: { + async add() { + this.adding = true; + + const process = async () => { + const user = await this.$root.api('users/show', parseAcct(this.username)); + await this.$root.api('admin/moderators/add', { userId: user.id }); + this.$root.alert({ + type: 'success', + text: this.$t('add-moderator.added') + }); + }; + + await process().catch(e => { + this.$root.alert({ + type: 'error', + text: e + }); + }); + + this.adding = false; + }, + } +}); +</script> + +<style lang="stylus" scoped> +.jnhmugbb + @media (min-width 500px) + padding 16px + +</style> diff --git a/src/client/app/common/views/components/note-menu.vue b/src/client/app/common/views/components/note-menu.vue index d848cb765d..7d15b4ed7f 100644 --- a/src/client/app/common/views/components/note-menu.vue +++ b/src/client/app/common/views/components/note-menu.vue @@ -55,7 +55,7 @@ export default Vue.extend({ } ] : [] ], [ - this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin ? [{ + this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin || this.$store.state.i.isModerator ? [{ icon: ['far', 'trash-alt'], text: this.$t('delete'), action: this.del diff --git a/src/client/app/desktop/views/components/ui.header.account.vue b/src/client/app/desktop/views/components/ui.header.account.vue index 79410273aa..a16f164556 100644 --- a/src/client/app/desktop/views/components/ui.header.account.vue +++ b/src/client/app/desktop/views/components/ui.header.account.vue @@ -58,7 +58,7 @@ <i><fa icon="angle-right"/></i> </p> </li> - <li v-if="$store.state.i.isAdmin"> + <li v-if="$store.state.i.isAdmin || $store.state.i.isModerator"> <a href="/admin"> <i><fa icon="terminal"/></i> <span>{{ $t('admin') }}</span> diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue index c529ae2617..4f085a5e6d 100644 --- a/src/client/app/mobile/views/components/ui.nav.vue +++ b/src/client/app/mobile/views/components/ui.nav.vue @@ -30,7 +30,7 @@ <ul> <li><a @click="search"><i><fa icon="search"/></i>{{ $t('search') }}<i><fa icon="angle-right"/></i></a></li> <li><router-link to="/i/settings" :data-active="$route.name == 'settings'"><i><fa icon="cog"/></i>{{ $t('settings') }}<i><fa icon="angle-right"/></i></router-link></li> - <li v-if="$store.getters.isSignedIn && $store.state.i.isAdmin"><a href="/admin"><i><fa icon="terminal"/></i><span>{{ $t('admin') }}</span><i><fa icon="angle-right"/></i></a></li> + <li v-if="$store.getters.isSignedIn && ($store.state.i.isAdmin || $store.state.i.isModerator)"><a href="/admin"><i><fa icon="terminal"/></i><span>{{ $t('admin') }}</span><i><fa icon="angle-right"/></i></a></li> <li @click="dark"><p><template v-if="$store.state.device.darkmode"><i><fa icon="moon"/></i></template><template v-else><i><fa :icon="['far', 'moon']"/></i></template><span>{{ $t('darkmode') }}</span></p></li> </ul> </div> diff --git a/src/models/user.ts b/src/models/user.ts index 2ca1917dc9..a5863de8da 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -99,6 +99,7 @@ export interface ILocalUser extends IUserBase { lastUsedAt: Date; isCat: boolean; isAdmin?: boolean; + isModerator?: boolean; isVerified?: boolean; twoFactorSecret: string; twoFactorEnabled: boolean; @@ -125,6 +126,7 @@ export interface IRemoteUser extends IUserBase { }; updatedAt: Date; isAdmin: false; + isModerator: false; } export type IUser = ILocalUser | IRemoteUser; diff --git a/src/server/api/call.ts b/src/server/api/call.ts index 6252537c0e..a3953d0439 100644 --- a/src/server/api/call.ts +++ b/src/server/api/call.ts @@ -29,6 +29,10 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any) return rej('YOU_ARE_NOT_ADMIN'); } + if (ep.meta.requireModerator && !user.isAdmin && !user.isModerator) { + return rej('YOU_ARE_NOT_MODERATOR'); + } + if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) { return rej('PERMISSION_DENIED'); } diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index e764ac2e95..6765a63e9f 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -31,6 +31,11 @@ export interface IEndpointMeta { requireAdmin?: boolean; /** + * 管理者またはモデレーターのみ使えるエンドポイントか否か + */ + requireModerator?: boolean; + + /** * エンドポイントのリミテーションに関するやつ * 省略した場合はリミテーションは無いものとして解釈されます。 * また、withCredential が false の場合はリミテーションを行うことはできません。 diff --git a/src/server/api/endpoints/admin/emoji/add.ts b/src/server/api/endpoints/admin/emoji/add.ts index 4f9a84e67e..91b5ff62d7 100644 --- a/src/server/api/endpoints/admin/emoji/add.ts +++ b/src/server/api/endpoints/admin/emoji/add.ts @@ -8,7 +8,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { name: { diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts index fd69fb0b29..428b274fed 100644 --- a/src/server/api/endpoints/admin/emoji/list.ts +++ b/src/server/api/endpoints/admin/emoji/list.ts @@ -8,7 +8,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { host: { diff --git a/src/server/api/endpoints/admin/emoji/remove.ts b/src/server/api/endpoints/admin/emoji/remove.ts index 32f1ced0c8..1d6ed11b63 100644 --- a/src/server/api/endpoints/admin/emoji/remove.ts +++ b/src/server/api/endpoints/admin/emoji/remove.ts @@ -9,7 +9,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { id: { diff --git a/src/server/api/endpoints/admin/emoji/update.ts b/src/server/api/endpoints/admin/emoji/update.ts index d0c2e6dafc..cbcc07fd48 100644 --- a/src/server/api/endpoints/admin/emoji/update.ts +++ b/src/server/api/endpoints/admin/emoji/update.ts @@ -9,7 +9,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { id: { diff --git a/src/server/api/endpoints/admin/invite.ts b/src/server/api/endpoints/admin/invite.ts index 056cb8aa75..ebfcb84452 100644 --- a/src/server/api/endpoints/admin/invite.ts +++ b/src/server/api/endpoints/admin/invite.ts @@ -8,7 +8,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: {} }; diff --git a/src/server/api/endpoints/admin/moderators/add.ts b/src/server/api/endpoints/admin/moderators/add.ts new file mode 100644 index 0000000000..4b4675c566 --- /dev/null +++ b/src/server/api/endpoints/admin/moderators/add.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import ID, { transform } from '../../../../../misc/cafy-id'; +import define from '../../../define'; +import User from '../../../../../models/user'; + +export const meta = { + desc: { + 'ja-JP': '指定したユーザーをモデレーターにします。', + 'en-US': 'Mark a user as moderator.' + }, + + requireCredential: true, + requireAdmin: true, + + params: { + userId: { + validator: $.type(ID), + transform: transform, + desc: { + 'ja-JP': '対象のユーザーID', + 'en-US': 'The user ID' + } + }, + } +}; + +export default define(meta, (ps) => new Promise(async (res, rej) => { + const user = await User.findOne({ + _id: ps.userId + }); + + if (user == null) { + return rej('user not found'); + } + + await User.update({ + _id: user._id + }, { + $set: { + isModerator: true + } + }); + + res(); +})); diff --git a/src/server/api/endpoints/admin/moderators/remove.ts b/src/server/api/endpoints/admin/moderators/remove.ts new file mode 100644 index 0000000000..2b9da61bb5 --- /dev/null +++ b/src/server/api/endpoints/admin/moderators/remove.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import ID, { transform } from '../../../../../misc/cafy-id'; +import define from '../../../define'; +import User from '../../../../../models/user'; + +export const meta = { + desc: { + 'ja-JP': '指定したユーザーをモデレーター解除します。', + 'en-US': 'Unmark a user as moderator.' + }, + + requireCredential: true, + requireAdmin: true, + + params: { + userId: { + validator: $.type(ID), + transform: transform, + desc: { + 'ja-JP': '対象のユーザーID', + 'en-US': 'The user ID' + } + }, + } +}; + +export default define(meta, (ps) => new Promise(async (res, rej) => { + const user = await User.findOne({ + _id: ps.userId + }); + + if (user == null) { + return rej('user not found'); + } + + await User.update({ + _id: user._id + }, { + $set: { + isModerator: false + } + }); + + res(); +})); diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts index 0ad0aab74c..5bbd387a20 100644 --- a/src/server/api/endpoints/admin/suspend-user.ts +++ b/src/server/api/endpoints/admin/suspend-user.ts @@ -10,7 +10,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { userId: { diff --git a/src/server/api/endpoints/admin/unsuspend-user.ts b/src/server/api/endpoints/admin/unsuspend-user.ts index 7c5eedee46..4b53246264 100644 --- a/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/src/server/api/endpoints/admin/unsuspend-user.ts @@ -10,7 +10,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { userId: { diff --git a/src/server/api/endpoints/admin/unverify-user.ts b/src/server/api/endpoints/admin/unverify-user.ts index d749e002e3..3e044ffed7 100644 --- a/src/server/api/endpoints/admin/unverify-user.ts +++ b/src/server/api/endpoints/admin/unverify-user.ts @@ -10,7 +10,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { userId: { diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 39d7ef86b9..1e4ff959d9 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -8,7 +8,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { broadcasts: { diff --git a/src/server/api/endpoints/admin/verify-user.ts b/src/server/api/endpoints/admin/verify-user.ts index 09efc2e803..996e044d18 100644 --- a/src/server/api/endpoints/admin/verify-user.ts +++ b/src/server/api/endpoints/admin/verify-user.ts @@ -10,7 +10,7 @@ export const meta = { }, requireCredential: true, - requireAdmin: true, + requireModerator: true, params: { userId: { diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index f7c3179909..b324b113c8 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -84,7 +84,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { }; } - if (me && me.isAdmin) { + if (me && (me.isAdmin || me.isModerator)) { response.hidedTags = instance.hidedTags; response.recaptchaSecretKey = instance.recaptchaSecretKey; response.proxyAccount = instance.proxyAccount; diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index a8f22ad405..aa11f7bf19 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -38,7 +38,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => { return rej('note not found'); } - if (!user.isAdmin && !note.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && !note.userId.equals(user._id)) { return rej('access denied'); } |