mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-10-31 05:24:13 +00:00 
			
		
		
		
	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: "メンバー" |   users: "メンバー" | ||||||
|   rename: "グループ名を変更" |   rename: "グループ名を変更" | ||||||
|   delete: "グループを削除" |   delete: "グループを削除" | ||||||
|  |   transfer: "グループを譲渡" | ||||||
|  |   transfer-are-you-sure: "グループ「$1」を「@$2」さんに譲渡しますか?" | ||||||
|  |   transferred: "グループを譲渡しました" | ||||||
|   remove-user: "このグループから削除" |   remove-user: "このグループから削除" | ||||||
|   delete-are-you-sure: "グループ「$1」を削除しますか?" |   delete-are-you-sure: "グループ「$1」を削除しますか?" | ||||||
|   deleted: "削除しました" |   deleted: "削除しました" | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
| 			<ui-margin> | 			<ui-margin> | ||||||
| 				<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button> | 				<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="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button> | ||||||
|  | 				<ui-button @click="transfer"><fa :icon="faCrown"/> {{ $t('transfer') }}</ui-button> | ||||||
| 			</ui-margin> | 			</ui-margin> | ||||||
| 		</section> | 		</section> | ||||||
| 	</ui-container> | 	</ui-container> | ||||||
|  | @ -28,9 +29,10 @@ | ||||||
| 					<div> | 					<div> | ||||||
| 						<header> | 						<header> | ||||||
| 							<b><mk-user-name :user="user"/></b> | 							<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> | 							<span class="username">@{{ user | acct }}</span> | ||||||
| 						</header> | 						</header> | ||||||
| 						<div> | 						<div v-if="group.owner !== user.id"> | ||||||
| 							<a @click="remove(user)">{{ $t('remove-user') }}</a> | 							<a @click="remove(user)">{{ $t('remove-user') }}</a> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
|  | @ -44,7 +46,7 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | 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'; | import { faTrashAlt } from '@fortawesome/free-regular-svg-icons'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
|  | @ -60,7 +62,7 @@ export default Vue.extend({ | ||||||
| 		return { | 		return { | ||||||
| 			group: null, | 			group: null, | ||||||
| 			users: [], | 			users: [], | ||||||
| 			faICursor, faTrashAlt, faUsers, faPlus | 			faCrown, faICursor, faTrashAlt, faUsers, faPlus | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | @ -78,6 +80,14 @@ export default Vue.extend({ | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	methods: { | 	methods: { | ||||||
|  | 		fetchGroup() { | ||||||
|  | 			this.$root.api('users/groups/show', { | ||||||
|  | 				groupId: this.group.id | ||||||
|  | 			}).then(group => { | ||||||
|  | 				this.group = group; | ||||||
|  | 			}) | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
| 		fetchUsers() { | 		fetchUsers() { | ||||||
| 			this.$root.api('users/show', { | 			this.$root.api('users/show', { | ||||||
| 				userIds: this.group.userIds | 				userIds: this.group.userIds | ||||||
|  | @ -97,8 +107,15 @@ export default Vue.extend({ | ||||||
| 				this.$root.api('users/groups/update', { | 				this.$root.api('users/groups/update', { | ||||||
| 					groupId: this.group.id, | 					groupId: this.group.id, | ||||||
| 					name: name | 					name: name | ||||||
|  | 				}).then(() => { | ||||||
|  | 					this.fetchGroup(); | ||||||
|  | 				}).catch(e => { | ||||||
|  | 					this.$root.dialog({ | ||||||
|  | 						type: 'error', | ||||||
|  | 						text: e | ||||||
|  | 					}); | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}) | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		del() { | 		del() { | ||||||
|  | @ -130,12 +147,17 @@ export default Vue.extend({ | ||||||
| 				groupId: this.group.id, | 				groupId: this.group.id, | ||||||
| 				userId: user.id | 				userId: user.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
|  | 				this.fetchGroup(); | ||||||
| 				this.fetchUsers(); | 				this.fetchUsers(); | ||||||
|  | 			}).catch(e => { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'error', | ||||||
|  | 					text: e | ||||||
|  | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		async invite() { | 		async invite() { | ||||||
| 			const t = this.$t('invited'); |  | ||||||
| 			const { result: user } = await this.$root.dialog({ | 			const { result: user } = await this.$root.dialog({ | ||||||
| 				user: { | 				user: { | ||||||
| 					local: true | 					local: true | ||||||
|  | @ -148,7 +170,44 @@ export default Vue.extend({ | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
| 					type: 'success', | 					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 | 			> header | ||||||
| 				color var(--text) | 				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 | 				> .username | ||||||
| 					margin-left 8px | 					margin-left 8px | ||||||
| 					opacity 0.7 | 					opacity 0.7 | ||||||
|  |  | ||||||
|  | @ -78,6 +78,7 @@ import { | ||||||
| 	faKey, | 	faKey, | ||||||
| 	faBan, | 	faBan, | ||||||
| 	faCogs, | 	faCogs, | ||||||
|  | 	faCrown, | ||||||
| 	faUnlockAlt, | 	faUnlockAlt, | ||||||
| 	faPuzzlePiece, | 	faPuzzlePiece, | ||||||
| 	faMobileAlt, | 	faMobileAlt, | ||||||
|  | @ -210,6 +211,7 @@ library.add( | ||||||
| 	faKey, | 	faKey, | ||||||
| 	faBan, | 	faBan, | ||||||
| 	faCogs, | 	faCogs, | ||||||
|  | 	faCrown, | ||||||
| 	faUnlockAlt, | 	faUnlockAlt, | ||||||
| 	faPuzzlePiece, | 	faPuzzlePiece, | ||||||
| 	faMobileAlt, | 	faMobileAlt, | ||||||
|  |  | ||||||
|  | @ -235,5 +235,8 @@ | ||||||
| 
 | 
 | ||||||
| 		pageBlockBorder: 'rgba(255, 255, 255, 0.1)', | 		pageBlockBorder: 'rgba(255, 255, 255, 0.1)', | ||||||
| 		pageBlockBorderHover: 'rgba(255, 255, 255, 0.15)', | 		pageBlockBorderHover: 'rgba(255, 255, 255, 0.15)', | ||||||
|  | 
 | ||||||
|  | 		groupUserListOwnerFg: '#f15f71', | ||||||
|  | 		groupUserListOwnerBg: '#5d282e' | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -235,5 +235,8 @@ | ||||||
| 
 | 
 | ||||||
| 		pageBlockBorder: 'rgba(0, 0, 0, 0.1)', | 		pageBlockBorder: 'rgba(0, 0, 0, 0.1)', | ||||||
| 		pageBlockBorderHover: 'rgba(0, 0, 0, 0.15)', | 		pageBlockBorderHover: 'rgba(0, 0, 0, 0.15)', | ||||||
|  | 
 | ||||||
|  | 		groupUserListOwnerFg: '#f15f71', | ||||||
|  | 		groupUserListOwnerBg: '#ffdfdf' | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ export class UserGroupRepository extends Repository<UserGroup> { | ||||||
| 			id: userGroup.id, | 			id: userGroup.id, | ||||||
| 			createdAt: userGroup.createdAt.toISOString(), | 			createdAt: userGroup.createdAt.toISOString(), | ||||||
| 			name: userGroup.name, | 			name: userGroup.name, | ||||||
|  | 			owner: userGroup.userId, | ||||||
| 			userIds: users.map(x => x.userId) | 			userIds: users.map(x => x.userId) | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  | @ -48,6 +49,11 @@ export const packedUserGroupSchema = { | ||||||
| 			optional: bool.false, nullable: bool.false, | 			optional: bool.false, nullable: bool.false, | ||||||
| 			description: 'The name of the UserGroup.' | 			description: 'The name of the UserGroup.' | ||||||
| 		}, | 		}, | ||||||
|  | 		owner: { | ||||||
|  | 			type: types.string, | ||||||
|  | 			nullable: bool.false, optional: bool.false, | ||||||
|  | 			format: 'id', | ||||||
|  | 		}, | ||||||
| 		userIds: { | 		userIds: { | ||||||
| 			type: types.array, | 			type: types.array, | ||||||
| 			nullable: bool.false, optional: bool.true, | 			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…
	
	Add table
		
		Reference in a new issue