summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-11-15 04:15:42 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-11-15 04:15:42 +0900
commit56d571c0f0f525263ea6257a5d5a2e7a9085e203 (patch)
tree69c40b1012f8c632e6601c50be9c7e2fb1a12e5c /src
parent[Client] Add missing icon (diff)
downloadsharkey-56d571c0f0f525263ea6257a5d5a2e7a9085e203.tar.gz
sharkey-56d571c0f0f525263ea6257a5d5a2e7a9085e203.tar.bz2
sharkey-56d571c0f0f525263ea6257a5d5a2e7a9085e203.zip
Moderator system
Closes #2357
Diffstat (limited to 'src')
-rw-r--r--src/client/app/admin/views/index.vue9
-rw-r--r--src/client/app/admin/views/moderators.vue61
-rw-r--r--src/client/app/common/views/components/note-menu.vue2
-rw-r--r--src/client/app/desktop/views/components/ui.header.account.vue2
-rw-r--r--src/client/app/mobile/views/components/ui.nav.vue2
-rw-r--r--src/models/user.ts2
-rw-r--r--src/server/api/call.ts4
-rw-r--r--src/server/api/endpoints.ts5
-rw-r--r--src/server/api/endpoints/admin/emoji/add.ts2
-rw-r--r--src/server/api/endpoints/admin/emoji/list.ts2
-rw-r--r--src/server/api/endpoints/admin/emoji/remove.ts2
-rw-r--r--src/server/api/endpoints/admin/emoji/update.ts2
-rw-r--r--src/server/api/endpoints/admin/invite.ts2
-rw-r--r--src/server/api/endpoints/admin/moderators/add.ts45
-rw-r--r--src/server/api/endpoints/admin/moderators/remove.ts45
-rw-r--r--src/server/api/endpoints/admin/suspend-user.ts2
-rw-r--r--src/server/api/endpoints/admin/unsuspend-user.ts2
-rw-r--r--src/server/api/endpoints/admin/unverify-user.ts2
-rw-r--r--src/server/api/endpoints/admin/update-meta.ts2
-rw-r--r--src/server/api/endpoints/admin/verify-user.ts2
-rw-r--r--src/server/api/endpoints/meta.ts2
-rw-r--r--src/server/api/endpoints/notes/delete.ts2
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');
}