Add group update / transfer API
This commit is contained in:
parent
a973bd56fe
commit
1092818203
8 changed files with 240 additions and 6 deletions
|
@ -762,6 +762,9 @@ common/views/components/user-group-editor.vue:
|
|||
users: "メンバー"
|
||||
rename: "グループ名を変更"
|
||||
delete: "グループを削除"
|
||||
transfer: "グループを譲渡"
|
||||
transfer-are-you-sure: "グループ「$1」を「@$2」さんに譲渡しますか?"
|
||||
transferred: "グループを譲渡しました"
|
||||
remove-user: "このグループから削除"
|
||||
delete-are-you-sure: "グループ「$1」を削除しますか?"
|
||||
deleted: "削除しました"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -235,5 +235,8 @@
|
|||
|
||||
pageBlockBorder: 'rgba(255, 255, 255, 0.1)',
|
||||
pageBlockBorderHover: 'rgba(255, 255, 255, 0.15)',
|
||||
|
||||
groupUserListOwnerFg: '#f15f71',
|
||||
groupUserListOwnerBg: '#5d282e'
|
||||
},
|
||||
}
|
||||
|
|
|
@ -235,5 +235,8 @@
|
|||
|
||||
pageBlockBorder: 'rgba(0, 0, 0, 0.1)',
|
||||
pageBlockBorderHover: 'rgba(0, 0, 0, 0.15)',
|
||||
|
||||
groupUserListOwnerFg: '#f15f71',
|
||||
groupUserListOwnerBg: '#ffdfdf'
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
86
src/server/api/endpoints/users/groups/transfer.ts
Normal file
86
src/server/api/endpoints/users/groups/transfer.ts
Normal file
|
@ -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);
|
||||
});
|
62
src/server/api/endpoints/users/groups/update.ts
Normal file
62
src/server/api/endpoints/users/groups/update.ts
Normal file
|
@ -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);
|
||||
});
|
Loading…
Reference in a new issue