summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSatsuki Yanagi <17376330+u1-liquid@users.noreply.github.com>2019-05-22 05:06:52 +0900
committerSatsuki Yanagi <17376330+u1-liquid@users.noreply.github.com>2019-05-22 05:06:58 +0900
commit10928182034f1db668de422cda6461bd31aaaa73 (patch)
treeeb36e3d13eacbe7548fc15c6b3c3853be0bf0b25 /src
parent11.18.1 (diff)
downloadmisskey-10928182034f1db668de422cda6461bd31aaaa73.tar.gz
misskey-10928182034f1db668de422cda6461bd31aaaa73.tar.bz2
misskey-10928182034f1db668de422cda6461bd31aaaa73.zip
Add group update / transfer API
Diffstat (limited to 'src')
-rw-r--r--src/client/app/common/views/pages/user-group-editor.vue81
-rw-r--r--src/client/app/init.ts2
-rw-r--r--src/client/themes/dark.json53
-rw-r--r--src/client/themes/light.json53
-rw-r--r--src/models/repositories/user-group.ts6
-rw-r--r--src/server/api/endpoints/users/groups/transfer.ts86
-rw-r--r--src/server/api/endpoints/users/groups/update.ts62
7 files changed, 237 insertions, 6 deletions
diff --git a/src/client/app/common/views/pages/user-group-editor.vue b/src/client/app/common/views/pages/user-group-editor.vue
index ef79689ae8..a32148cd7f 100644
--- a/src/client/app/common/views/pages/user-group-editor.vue
+++ b/src/client/app/common/views/pages/user-group-editor.vue
@@ -7,6 +7,7 @@
<ui-margin>
<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
+ <ui-button @click="transfer"><fa :icon="faCrown"/> {{ $t('transfer') }}</ui-button>
</ui-margin>
</section>
</ui-container>
@@ -28,9 +29,10 @@
<div>
<header>
<b><mk-user-name :user="user"/></b>
+ <span class="is-owner" v-if="group.owner === user.id">owner</span>
<span class="username">@{{ user | acct }}</span>
</header>
- <div>
+ <div v-if="group.owner !== user.id">
<a @click="remove(user)">{{ $t('remove-user') }}</a>
</div>
</div>
@@ -44,7 +46,7 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
-import { faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { faCrown, faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
@@ -60,7 +62,7 @@ export default Vue.extend({
return {
group: null,
users: [],
- faICursor, faTrashAlt, faUsers, faPlus
+ faCrown, faICursor, faTrashAlt, faUsers, faPlus
};
},
@@ -78,6 +80,14 @@ export default Vue.extend({
},
methods: {
+ fetchGroup() {
+ this.$root.api('users/groups/show', {
+ groupId: this.group.id
+ }).then(group => {
+ this.group = group;
+ })
+ },
+
fetchUsers() {
this.$root.api('users/show', {
userIds: this.group.userIds
@@ -97,8 +107,15 @@ export default Vue.extend({
this.$root.api('users/groups/update', {
groupId: this.group.id,
name: name
+ }).then(() => {
+ this.fetchGroup();
+ }).catch(e => {
+ this.$root.dialog({
+ type: 'error',
+ text: e
+ });
});
- });
+ })
},
del() {
@@ -130,12 +147,17 @@ export default Vue.extend({
groupId: this.group.id,
userId: user.id
}).then(() => {
+ this.fetchGroup();
this.fetchUsers();
+ }).catch(e => {
+ this.$root.dialog({
+ type: 'error',
+ text: e
+ });
});
},
async invite() {
- const t = this.$t('invited');
const { result: user } = await this.$root.dialog({
user: {
local: true
@@ -148,7 +170,44 @@ export default Vue.extend({
}).then(() => {
this.$root.dialog({
type: 'success',
- text: t
+ text: this.$t('invited')
+ });
+ }).catch(e => {
+ this.$root.dialog({
+ type: 'error',
+ text: e
+ });
+ });
+ },
+
+ async transfer() {
+ const { result: user } = await this.$root.dialog({
+ user: {
+ local: true
+ }
+ });
+ if (user == null) return;
+
+ this.$root.dialog({
+ type: 'warning',
+ text: this.$t('transfer-are-you-sure').replace('$1', this.group.name).replace('$2', user.username),
+ showCancelButton: true
+ }).then(({ canceled }) => {
+ if (canceled) return;
+
+ this.$root.api('users/groups/transfer', {
+ groupId: this.group.id,
+ userId: user.id
+ }).then(() => {
+ this.$root.dialog({
+ type: 'success',
+ text: this.$t('transferred')
+ });
+ }).catch(e => {
+ this.$root.dialog({
+ type: 'error',
+ text: e
+ });
});
});
}
@@ -179,6 +238,16 @@ export default Vue.extend({
> header
color var(--text)
+ > .is-owner
+ flex-shrink 0
+ align-self center
+ margin-left 8px
+ padding 1px 6px
+ font-size 80%
+ background var(--groupUserListOwnerBg)
+ color var(--groupUserListOwnerFg)
+ border-radius 3px
+
> .username
margin-left 8px
opacity 0.7
diff --git a/src/client/app/init.ts b/src/client/app/init.ts
index da7baff4fe..52da380e84 100644
--- a/src/client/app/init.ts
+++ b/src/client/app/init.ts
@@ -78,6 +78,7 @@ import {
faKey,
faBan,
faCogs,
+ faCrown,
faUnlockAlt,
faPuzzlePiece,
faMobileAlt,
@@ -210,6 +211,7 @@ library.add(
faKey,
faBan,
faCogs,
+ faCrown,
faUnlockAlt,
faPuzzlePiece,
faMobileAlt,
diff --git a/src/client/themes/dark.json5 b/src/client/themes/dark.json5
index 8e0c726b4c..0665d59901 100644
--- a/src/client/themes/dark.json5
+++ b/src/client/themes/dark.json5
@@ -235,5 +235,8 @@
pageBlockBorder: 'rgba(255, 255, 255, 0.1)',
pageBlockBorderHover: 'rgba(255, 255, 255, 0.15)',
+
+ groupUserListOwnerFg: '#f15f71',
+ groupUserListOwnerBg: '#5d282e'
},
}
diff --git a/src/client/themes/light.json5 b/src/client/themes/light.json5
index 1fff18176a..cbe456ca5d 100644
--- a/src/client/themes/light.json5
+++ b/src/client/themes/light.json5
@@ -235,5 +235,8 @@
pageBlockBorder: 'rgba(0, 0, 0, 0.1)',
pageBlockBorderHover: 'rgba(0, 0, 0, 0.15)',
+
+ groupUserListOwnerFg: '#f15f71',
+ groupUserListOwnerBg: '#ffdfdf'
},
}
diff --git a/src/models/repositories/user-group.ts b/src/models/repositories/user-group.ts
index 8bb1ae8330..dbbe8bf84c 100644
--- a/src/models/repositories/user-group.ts
+++ b/src/models/repositories/user-group.ts
@@ -21,6 +21,7 @@ export class UserGroupRepository extends Repository<UserGroup> {
id: userGroup.id,
createdAt: userGroup.createdAt.toISOString(),
name: userGroup.name,
+ owner: userGroup.userId,
userIds: users.map(x => x.userId)
};
}
@@ -48,6 +49,11 @@ export const packedUserGroupSchema = {
optional: bool.false, nullable: bool.false,
description: 'The name of the UserGroup.'
},
+ owner: {
+ type: types.string,
+ nullable: bool.false, optional: bool.false,
+ format: 'id',
+ },
userIds: {
type: types.array,
nullable: bool.false, optional: bool.true,
diff --git a/src/server/api/endpoints/users/groups/transfer.ts b/src/server/api/endpoints/users/groups/transfer.ts
new file mode 100644
index 0000000000..3baa182abf
--- /dev/null
+++ b/src/server/api/endpoints/users/groups/transfer.ts
@@ -0,0 +1,86 @@
+import $ from 'cafy';
+import { ID } from '../../../../../misc/cafy-id';
+import define from '../../../define';
+import { ApiError } from '../../../error';
+import { getUser } from '../../../common/getters';
+import { UserGroups, UserGroupJoinings } from '../../../../../models';
+
+export const meta = {
+ desc: {
+ 'ja-JP': '指定したユーザーグループを指定したユーザーグループ内のユーザーに譲渡します。',
+ 'en-US': 'Transfer user group ownership to another user in group.'
+ },
+
+ tags: ['groups', 'users'],
+
+ requireCredential: true,
+
+ kind: 'write:user-groups',
+
+ params: {
+ groupId: {
+ validator: $.type(ID),
+ },
+
+ userId: {
+ validator: $.type(ID),
+ desc: {
+ 'ja-JP': '対象のユーザーのID',
+ 'en-US': 'Target user ID'
+ }
+ },
+ },
+
+ errors: {
+ noSuchGroup: {
+ message: 'No such group.',
+ code: 'NO_SUCH_GROUP',
+ id: '8e31d36b-2f88-4ccd-a438-e2d78a9162db'
+ },
+
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: '711f7ebb-bbb9-4dfa-b540-b27809fed5e9'
+ },
+
+ noSuchGroupMember: {
+ message: 'No such group member.',
+ code: 'NO_SUCH_GROUP_MEMBER',
+ id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4'
+ },
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await UserGroups.findOne({
+ id: ps.groupId,
+ userId: me.id,
+ });
+
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+
+ // Fetch the user
+ const user = await getUser(ps.userId).catch(e => {
+ if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+
+ const joining = await UserGroupJoinings.findOne({
+ userGroupId: userGroup.id,
+ userId: user.id
+ });
+
+ if (!joining) {
+ throw new ApiError(meta.errors.noSuchGroupMember);
+ }
+
+ await UserGroups.update(userGroup.id, {
+ userId: ps.userId
+ });
+
+ return await UserGroups.pack(userGroup.id);
+});
diff --git a/src/server/api/endpoints/users/groups/update.ts b/src/server/api/endpoints/users/groups/update.ts
new file mode 100644
index 0000000000..ad9a1faa23
--- /dev/null
+++ b/src/server/api/endpoints/users/groups/update.ts
@@ -0,0 +1,62 @@
+import $ from 'cafy';
+import { ID } from '../../../../../misc/cafy-id';
+import define from '../../../define';
+import { ApiError } from '../../../error';
+import { UserGroups } from '../../../../../models';
+
+export const meta = {
+ desc: {
+ 'ja-JP': '指定したユーザーグループを更新します。',
+ 'en-US': 'Update a user group'
+ },
+
+ tags: ['groups'],
+
+ requireCredential: true,
+
+ kind: 'write:user-groups',
+
+ params: {
+ groupId: {
+ validator: $.type(ID),
+ desc: {
+ 'ja-JP': '対象となるユーザーグループのID',
+ 'en-US': 'ID of target user group'
+ }
+ },
+
+ name: {
+ validator: $.str.range(1, 100),
+ desc: {
+ 'ja-JP': 'このユーザーグループの名前',
+ 'en-US': 'name of this user group'
+ }
+ }
+ },
+
+ errors: {
+ noSuchGroup: {
+ message: 'No such group.',
+ code: 'NO_SUCH_GROUP',
+ id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6'
+ },
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await UserGroups.findOne({
+ id: ps.groupId,
+ userId: me.id
+ });
+
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+
+ await UserGroups.update(userGroup.id, {
+ name: ps.name
+ });
+
+ return await UserGroups.pack(userGroup.id);
+});