Resolve #4941
This commit is contained in:
		
							parent
							
								
									56678cbac0
								
							
						
					
					
						commit
						e103904a04
					
				
					 15 changed files with 379 additions and 34 deletions
				
			
		|  | @ -485,6 +485,7 @@ common/views/components/messaging.vue: | |||
|   group: "グループ" | ||||
|   start-with-user: "ユーザーとトークを開始" | ||||
|   start-with-group: "グループとトークを開始" | ||||
|   select-group: "グループを選択してください" | ||||
| 
 | ||||
| common/views/components/messaging-room.vue: | ||||
|   not-talked-user: "このユーザーとの会話はありません" | ||||
|  | @ -764,7 +765,8 @@ common/views/components/user-group-editor.vue: | |||
|   remove-user: "このグループから削除" | ||||
|   delete-are-you-sure: "グループ「$1」を削除しますか?" | ||||
|   deleted: "削除しました" | ||||
|   add-user: "メンバーを追加" | ||||
|   invite: "招待" | ||||
|   invited: "招待を送信しました" | ||||
| 
 | ||||
| common/views/components/user-lists.vue: | ||||
|   user-lists: "リスト" | ||||
|  | @ -775,6 +777,11 @@ common/views/components/user-groups.vue: | |||
|   user-groups: "グループ" | ||||
|   create-group: "グループを作成" | ||||
|   group-name: "グループ名" | ||||
|   owned-groups: "自分のグループ" | ||||
|   joined-groups: "参加しているグループ" | ||||
|   invites: "招待" | ||||
|   accept-invite: "参加" | ||||
|   reject-invite: "拒否" | ||||
| 
 | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|  |  | |||
							
								
								
									
										25
									
								
								migration/1558257926829-UserGroupInvite.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								migration/1558257926829-UserGroupInvite.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
| 
 | ||||
| export class UserGroupInvite1558257926829 implements MigrationInterface { | ||||
| 
 | ||||
|     public async up(queryRunner: QueryRunner): Promise<any> { | ||||
|         await queryRunner.query(`CREATE TABLE "user_group_invite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_3893884af0d3a5f4d01e7921a97" PRIMARY KEY ("id"))`); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_1039988afa3bf991185b277fe0" ON "user_group_invite" ("userId") `); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_e10924607d058004304611a436" ON "user_group_invite" ("userGroupId") `); | ||||
|         await queryRunner.query(`CREATE UNIQUE INDEX "IDX_78787741f9010886796f2320a4" ON "user_group_invite" ("userId", "userGroupId") `); | ||||
|         await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d9ecaed8c6dc43f3592c229282" ON "user_group_joining" ("userId", "userGroupId") `); | ||||
|         await queryRunner.query(`ALTER TABLE "user_group_invite" ADD CONSTRAINT "FK_1039988afa3bf991185b277fe03" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||||
|         await queryRunner.query(`ALTER TABLE "user_group_invite" ADD CONSTRAINT "FK_e10924607d058004304611a436a" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||||
|     } | ||||
| 
 | ||||
|     public async down(queryRunner: QueryRunner): Promise<any> { | ||||
|         await queryRunner.query(`ALTER TABLE "user_group_invite" DROP CONSTRAINT "FK_e10924607d058004304611a436a"`); | ||||
|         await queryRunner.query(`ALTER TABLE "user_group_invite" DROP CONSTRAINT "FK_1039988afa3bf991185b277fe03"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_d9ecaed8c6dc43f3592c229282"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_78787741f9010886796f2320a4"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_e10924607d058004304611a436"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_1039988afa3bf991185b277fe0"`); | ||||
|         await queryRunner.query(`DROP TABLE "user_group_invite"`); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -217,12 +217,13 @@ export default Vue.extend({ | |||
| 			this.navigate(user); | ||||
| 		}, | ||||
| 		async startGroup() { | ||||
| 			const groups = await this.$root.api('users/groups/joined'); | ||||
| 			const groups1 = await this.$root.api('users/groups/owned'); | ||||
| 			const groups2 = await this.$root.api('users/groups/joined'); | ||||
| 			const { canceled, result: group } = await this.$root.dialog({ | ||||
| 				type: null, | ||||
| 				title: this.$t('select-group'), | ||||
| 				select: { | ||||
| 					items: groups.map(group => ({ | ||||
| 					items: groups1.concat(groups2).map(group => ({ | ||||
| 						value: group, text: group.name | ||||
| 					})) | ||||
| 				}, | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
| 
 | ||||
| 		<section> | ||||
| 			<ui-margin> | ||||
| 				<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('add-user') }}</ui-button> | ||||
| 				<ui-button @click="invite()"><fa :icon="faPlus"/> {{ $t('invite') }}</ui-button> | ||||
| 			</ui-margin> | ||||
| 			<sequential-entrance animation="entranceFromTop" delay="25"> | ||||
| 				<div class="kjlrfbes" v-for="user in users"> | ||||
|  | @ -134,18 +134,22 @@ export default Vue.extend({ | |||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		async add() { | ||||
| 		async invite() { | ||||
| 			const t = this.$t('invited'); | ||||
| 			const { result: user } = await this.$root.dialog({ | ||||
| 				user: { | ||||
| 					local: true | ||||
| 				} | ||||
| 			}); | ||||
| 			if (user == null) return; | ||||
| 			this.$root.api('users/groups/push', { | ||||
| 			this.$root.api('users/groups/invite', { | ||||
| 				groupId: this.group.id, | ||||
| 				userId: user.id | ||||
| 			}).then(() => { | ||||
| 				this.fetchUsers(); | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: t | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,36 +1,70 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<ui-container> | ||||
| 	<template #header><fa :icon="faUsers"/> {{ $t('user-groups') }}</template> | ||||
| 		<template #header><fa :icon="faUsers"/> {{ $t('owned-groups') }}</template> | ||||
| 		<ui-margin> | ||||
| 			<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-group') }}</ui-button> | ||||
| 		</ui-margin> | ||||
| 	<div class="hwgkdrbl" v-for="group in groups" :key="group.id"> | ||||
| 		<div class="hwgkdrbl" v-for="group in ownedGroups" :key="group.id"> | ||||
| 			<ui-hr/> | ||||
| 			<ui-margin> | ||||
| 				<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link> | ||||
| 			</ui-margin> | ||||
| 		</div> | ||||
| 	</ui-container> | ||||
| 
 | ||||
| 	<ui-container> | ||||
| 		<template #header><fa :icon="faUsers"/> {{ $t('joined-groups') }}</template> | ||||
| 		<div class="hwgkdrbl" v-for="(group, i) in joinedGroups" :key="group.id"> | ||||
| 			<ui-hr v-if="i != 0"/> | ||||
| 			<ui-margin> | ||||
| 				<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link> | ||||
| 			</ui-margin> | ||||
| 		</div> | ||||
| 	</ui-container> | ||||
| 
 | ||||
| 	<ui-container> | ||||
| 		<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template> | ||||
| 		<div class="fvlojuur" v-for="(invite, i) in invites" :key="invite.id"> | ||||
| 			<ui-hr v-if="i != 0"/> | ||||
| 			<ui-margin> | ||||
| 				<div class="name">{{ invite.group.name }}</div> | ||||
| 				<ui-horizon-group> | ||||
| 					<ui-button @click="acceptInvite(invite)"><fa :icon="faCheck"/> {{ $t('accept-invite') }}</ui-button> | ||||
| 					<ui-button @click="rejectInvite(invite)"><fa :icon="faBan"/> {{ $t('reject-invite') }}</ui-button> | ||||
| 				</ui-horizon-group> | ||||
| 			</ui-margin> | ||||
| 		</div> | ||||
| 	</ui-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
| import { faUsers, faPlus } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faUsers, faPlus, faCheck, faBan, faEnvelopeOpenText } from '@fortawesome/free-solid-svg-icons'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('common/views/components/user-groups.vue'), | ||||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			groups: [], | ||||
| 			faUsers, faPlus | ||||
| 			ownedGroups: [], | ||||
| 			joinedGroups: [], | ||||
| 			invites: [], | ||||
| 			faUsers, faPlus, faCheck, faBan, faEnvelopeOpenText | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.$root.api('users/groups/owned').then(groups => { | ||||
| 			this.fetching = false; | ||||
| 			this.groups = groups; | ||||
| 			this.ownedGroups = groups; | ||||
| 		}); | ||||
| 
 | ||||
| 		this.$root.api('users/groups/joined').then(groups => { | ||||
| 			this.joinedGroups = groups; | ||||
| 		}); | ||||
| 
 | ||||
| 		this.$root.api('i/user-group-invites').then(invites => { | ||||
| 			this.invites = invites; | ||||
| 		}); | ||||
| 
 | ||||
| 		this.$emit('init', { | ||||
|  | @ -45,13 +79,35 @@ export default Vue.extend({ | |||
| 				input: true | ||||
| 			}).then(async ({ canceled, result: name }) => { | ||||
| 				if (canceled) return; | ||||
| 				const list = await this.$root.api('users/groups/create', { | ||||
| 				const group = await this.$root.api('users/groups/create', { | ||||
| 					name | ||||
| 				}); | ||||
| 
 | ||||
| 				this.groups.push(list) | ||||
| 				this.ownedGroups.push(group) | ||||
| 			}); | ||||
| 		}, | ||||
| 		acceptInvite(invite) { | ||||
| 			this.$root.api('users/groups/invitations/accept', { | ||||
| 				inviteId: invite.id | ||||
| 			}).then(() => { | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
| 					splash: true | ||||
| 				}); | ||||
| 				this.$root.api('i/user-group-invites').then(invites => { | ||||
| 					this.invites = invites; | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
| 		rejectInvite(invite) { | ||||
| 			this.$root.api('users/groups/invitations/reject', { | ||||
| 				inviteId: invite.id | ||||
| 			}).then(() => { | ||||
| 				this.$root.api('i/user-group-invites').then(invites => { | ||||
| 					this.invites = invites; | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ import { UserList } from '../models/entities/user-list'; | |||
| import { UserListJoining } from '../models/entities/user-list-joining'; | ||||
| import { UserGroup } from '../models/entities/user-group'; | ||||
| import { UserGroupJoining } from '../models/entities/user-group-joining'; | ||||
| import { UserGroupInvite } from '../models/entities/user-group-invite'; | ||||
| import { Hashtag } from '../models/entities/hashtag'; | ||||
| import { NoteFavorite } from '../models/entities/note-favorite'; | ||||
| import { AbuseUserReport } from '../models/entities/abuse-user-report'; | ||||
|  | @ -110,6 +111,7 @@ export function initDb(justBorrow = false, sync = false, log = false) { | |||
| 			UserListJoining, | ||||
| 			UserGroup, | ||||
| 			UserGroupJoining, | ||||
| 			UserGroupInvite, | ||||
| 			UserNotePining, | ||||
| 			Following, | ||||
| 			FollowRequest, | ||||
|  |  | |||
							
								
								
									
										42
									
								
								src/models/entities/user-group-invite.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/models/entities/user-group-invite.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; | ||||
| import { User } from './user'; | ||||
| import { UserGroup } from './user-group'; | ||||
| import { id } from '../id'; | ||||
| 
 | ||||
| @Entity() | ||||
| @Index(['userId', 'userGroupId'], { unique: true }) | ||||
| export class UserGroupInvite { | ||||
| 	@PrimaryColumn(id()) | ||||
| 	public id: string; | ||||
| 
 | ||||
| 	@Column('timestamp with time zone', { | ||||
| 		comment: 'The created date of the UserGroupInvite.' | ||||
| 	}) | ||||
| 	public createdAt: Date; | ||||
| 
 | ||||
| 	@Index() | ||||
| 	@Column({ | ||||
| 		...id(), | ||||
| 		comment: 'The user ID.' | ||||
| 	}) | ||||
| 	public userId: User['id']; | ||||
| 
 | ||||
| 	@ManyToOne(type => User, { | ||||
| 		onDelete: 'CASCADE' | ||||
| 	}) | ||||
| 	@JoinColumn() | ||||
| 	public user: User | null; | ||||
| 
 | ||||
| 	@Index() | ||||
| 	@Column({ | ||||
| 		...id(), | ||||
| 		comment: 'The group ID.' | ||||
| 	}) | ||||
| 	public userGroupId: UserGroup['id']; | ||||
| 
 | ||||
| 	@ManyToOne(type => UserGroup, { | ||||
| 		onDelete: 'CASCADE' | ||||
| 	}) | ||||
| 	@JoinColumn() | ||||
| 	public userGroup: UserGroup | null; | ||||
| } | ||||
|  | @ -4,6 +4,7 @@ import { UserGroup } from './user-group'; | |||
| import { id } from '../id'; | ||||
| 
 | ||||
| @Entity() | ||||
| @Index(['userId', 'userGroupId'], { unique: true }) | ||||
| export class UserGroupJoining { | ||||
| 	@PrimaryColumn(id()) | ||||
| 	public id: string; | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ import { UserListRepository } from './repositories/user-list'; | |||
| import { UserListJoining } from './entities/user-list-joining'; | ||||
| import { UserGroupRepository } from './repositories/user-group'; | ||||
| import { UserGroupJoining } from './entities/user-group-joining'; | ||||
| import { UserGroupInviteRepository } from './repositories/user-group-invite'; | ||||
| import { FollowRequestRepository } from './repositories/follow-request'; | ||||
| import { MutingRepository } from './repositories/muting'; | ||||
| import { BlockingRepository } from './repositories/blocking'; | ||||
|  | @ -56,6 +57,7 @@ export const UserLists = getCustomRepository(UserListRepository); | |||
| export const UserListJoinings = getRepository(UserListJoining); | ||||
| export const UserGroups = getCustomRepository(UserGroupRepository); | ||||
| export const UserGroupJoinings = getRepository(UserGroupJoining); | ||||
| export const UserGroupInvites = getCustomRepository(UserGroupInviteRepository); | ||||
| export const UserNotePinings = getRepository(UserNotePining); | ||||
| export const Followings = getCustomRepository(FollowingRepository); | ||||
| export const FollowRequests = getCustomRepository(FollowRequestRepository); | ||||
|  |  | |||
							
								
								
									
										24
									
								
								src/models/repositories/user-group-invite.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/models/repositories/user-group-invite.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| import { EntityRepository, Repository } from 'typeorm'; | ||||
| import { UserGroupInvite } from '../entities/user-group-invite'; | ||||
| import { UserGroups } from '..'; | ||||
| import { ensure } from '../../prelude/ensure'; | ||||
| 
 | ||||
| @EntityRepository(UserGroupInvite) | ||||
| export class UserGroupInviteRepository extends Repository<UserGroupInvite> { | ||||
| 	public async pack( | ||||
| 		src: UserGroupInvite['id'] | UserGroupInvite, | ||||
| 	) { | ||||
| 		const invite = typeof src === 'object' ? src : await this.findOne(src).then(ensure); | ||||
| 
 | ||||
| 		return { | ||||
| 			id: invite.id, | ||||
| 			group: await UserGroups.pack(invite.userGroup || invite.userGroupId), | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	public packMany( | ||||
| 		invites: any[], | ||||
| 	) { | ||||
| 		return Promise.all(invites.map(x => this.pack(x))); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										45
									
								
								src/server/api/endpoints/i/user-group-invites.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/server/api/endpoints/i/user-group-invites.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| import $ from 'cafy'; | ||||
| import { ID } from '../../../../misc/cafy-id'; | ||||
| import define from '../../define'; | ||||
| import { UserGroupInvites } from '../../../../models'; | ||||
| import { makePaginationQuery } from '../../common/make-pagination-query'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': 'グループへの招待一覧を取得します。', | ||||
| 		'en-US': 'Get user group invitations.' | ||||
| 	}, | ||||
| 
 | ||||
| 	tags: ['account', 'groups'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 
 | ||||
| 	kind: 'read:user-groups', | ||||
| 
 | ||||
| 	params: { | ||||
| 		limit: { | ||||
| 			validator: $.optional.num.range(1, 100), | ||||
| 			default: 10 | ||||
| 		}, | ||||
| 
 | ||||
| 		sinceId: { | ||||
| 			validator: $.optional.type(ID), | ||||
| 		}, | ||||
| 
 | ||||
| 		untilId: { | ||||
| 			validator: $.optional.type(ID), | ||||
| 		}, | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps, user) => { | ||||
| 	const query = makePaginationQuery(UserGroupInvites.createQueryBuilder('invite'), ps.sinceId, ps.untilId) | ||||
| 		.andWhere(`invite.userId = :meId`, { meId: user.id }) | ||||
| 		.leftJoinAndSelect('invite.userGroup', 'user_group'); | ||||
| 
 | ||||
| 	const invites = await query | ||||
| 		.take(ps.limit!) | ||||
| 		.getMany(); | ||||
| 
 | ||||
| 	return await UserGroupInvites.packMany(invites); | ||||
| }); | ||||
							
								
								
									
										63
									
								
								src/server/api/endpoints/users/groups/invitations/accept.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/server/api/endpoints/users/groups/invitations/accept.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| import $ from 'cafy'; | ||||
| import { ID } from '../../../../../../misc/cafy-id'; | ||||
| import define from '../../../../define'; | ||||
| import { ApiError } from '../../../../error'; | ||||
| import { UserGroupJoinings, UserGroupInvites } from '../../../../../../models'; | ||||
| import { genId } from '../../../../../../misc/gen-id'; | ||||
| import { UserGroupJoining } from '../../../../../../models/entities/user-group-joining'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': 'ユーザーグループへの招待を承認します。', | ||||
| 		'en-US': 'Accept invite of a user group.' | ||||
| 	}, | ||||
| 
 | ||||
| 	tags: ['groups', 'users'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 
 | ||||
| 	kind: 'write:user-groups', | ||||
| 
 | ||||
| 	params: { | ||||
| 		inviteId: { | ||||
| 			validator: $.type(ID), | ||||
| 			desc: { | ||||
| 				'ja-JP': '招待ID', | ||||
| 				'en-US': 'The invite ID' | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	errors: { | ||||
| 		noSuchInvitation: { | ||||
| 			message: 'No such invitation.', | ||||
| 			code: 'NO_SUCH_INVITATION', | ||||
| 			id: '98c11eca-c890-4f42-9806-c8c8303ebb5e' | ||||
| 		}, | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps, user) => { | ||||
| 	// Fetch the invitation
 | ||||
| 	const invite = await UserGroupInvites.findOne({ | ||||
| 		id: ps.inviteId, | ||||
| 	}); | ||||
| 
 | ||||
| 	if (invite == null) { | ||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | ||||
| 	} | ||||
| 
 | ||||
| 	if (invite.userId !== user.id) { | ||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | ||||
| 	} | ||||
| 
 | ||||
| 	// Push the user
 | ||||
| 	await UserGroupJoinings.save({ | ||||
| 		id: genId(), | ||||
| 		createdAt: new Date(), | ||||
| 		userId: user.id, | ||||
| 		userGroupId: invite.userGroupId | ||||
| 	} as UserGroupJoining); | ||||
| 
 | ||||
| 	UserGroupInvites.delete(invite.id); | ||||
| }); | ||||
							
								
								
									
										53
									
								
								src/server/api/endpoints/users/groups/invitations/reject.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/server/api/endpoints/users/groups/invitations/reject.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| import $ from 'cafy'; | ||||
| import { ID } from '../../../../../../misc/cafy-id'; | ||||
| import define from '../../../../define'; | ||||
| import { ApiError } from '../../../../error'; | ||||
| import { UserGroupInvites } from '../../../../../../models'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': 'ユーザーグループへの招待を拒否します。', | ||||
| 		'en-US': 'Reject invite of a user group.' | ||||
| 	}, | ||||
| 
 | ||||
| 	tags: ['groups', 'users'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 
 | ||||
| 	kind: 'write:user-groups', | ||||
| 
 | ||||
| 	params: { | ||||
| 		inviteId: { | ||||
| 			validator: $.type(ID), | ||||
| 			desc: { | ||||
| 				'ja-JP': '招待ID', | ||||
| 				'en-US': 'The invite ID' | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	errors: { | ||||
| 		noSuchInvitation: { | ||||
| 			message: 'No such invitation.', | ||||
| 			code: 'NO_SUCH_INVITATION', | ||||
| 			id: 'ad7471d4-2cd9-44b4-ac68-e7136b4ce656' | ||||
| 		}, | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps, user) => { | ||||
| 	// Fetch the invitation
 | ||||
| 	const invite = await UserGroupInvites.findOne({ | ||||
| 		id: ps.inviteId, | ||||
| 	}); | ||||
| 
 | ||||
| 	if (invite == null) { | ||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | ||||
| 	} | ||||
| 
 | ||||
| 	if (invite.userId !== user.id) { | ||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | ||||
| 	} | ||||
| 
 | ||||
| 	await UserGroupInvites.delete(invite.id); | ||||
| }); | ||||
|  | @ -3,14 +3,14 @@ import { ID } from '../../../../../misc/cafy-id'; | |||
| import define from '../../../define'; | ||||
| import { ApiError } from '../../../error'; | ||||
| import { getUser } from '../../../common/getters'; | ||||
| import { UserGroups, UserGroupJoinings } from '../../../../../models'; | ||||
| import { UserGroups, UserGroupJoinings, UserGroupInvites } from '../../../../../models'; | ||||
| import { genId } from '../../../../../misc/gen-id'; | ||||
| import { UserGroupJoining } from '../../../../../models/entities/user-group-joining'; | ||||
| import { UserGroupInvite } from '../../../../../models/entities/user-group-invite'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': '指定したユーザーグループに指定したユーザーを追加します。', | ||||
| 		'en-US': 'Add a user to a user group.' | ||||
| 		'ja-JP': '指定したユーザーグループに指定したユーザーを招待します。', | ||||
| 		'en-US': 'Invite a user to a user group.' | ||||
| 	}, | ||||
| 
 | ||||
| 	tags: ['groups', 'users'], | ||||
|  | @ -50,6 +50,12 @@ export const meta = { | |||
| 			message: 'That user has already been added to that group.', | ||||
| 			code: 'ALREADY_ADDED', | ||||
| 			id: '7e35c6a0-39b2-4488-aea6-6ee20bd5da2c' | ||||
| 		}, | ||||
| 
 | ||||
| 		alreadyInvited: { | ||||
| 			message: 'That user has already been invited to that group.', | ||||
| 			code: 'ALREADY_INVITED', | ||||
| 			id: 'ee0f58b4-b529-4d13-b761-b9a3e69f97e6' | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | @ -71,20 +77,28 @@ export default define(meta, async (ps, me) => { | |||
| 		throw e; | ||||
| 	}); | ||||
| 
 | ||||
| 	const exist = await UserGroupJoinings.findOne({ | ||||
| 	const joining = await UserGroupJoinings.findOne({ | ||||
| 		userGroupId: userGroup.id, | ||||
| 		userId: user.id | ||||
| 	}); | ||||
| 
 | ||||
| 	if (exist) { | ||||
| 	if (joining) { | ||||
| 		throw new ApiError(meta.errors.alreadyAdded); | ||||
| 	} | ||||
| 
 | ||||
| 	// Push the user
 | ||||
| 	await UserGroupJoinings.save({ | ||||
| 	const invite = await UserGroupInvites.findOne({ | ||||
| 		userGroupId: userGroup.id, | ||||
| 		userId: user.id | ||||
| 	}); | ||||
| 
 | ||||
| 	if (invite) { | ||||
| 		throw new ApiError(meta.errors.alreadyInvited); | ||||
| 	} | ||||
| 
 | ||||
| 	await UserGroupInvites.save({ | ||||
| 		id: genId(), | ||||
| 		createdAt: new Date(), | ||||
| 		userId: user.id, | ||||
| 		userGroupId: userGroup.id | ||||
| 	} as UserGroupJoining); | ||||
| 	} as UserGroupInvite); | ||||
| }); | ||||
|  | @ -1,6 +1,7 @@ | |||
| import define from '../../../define'; | ||||
| import { UserGroups, UserGroupJoinings } from '../../../../../models'; | ||||
| import { types, bool } from '../../../../../misc/schema'; | ||||
| import { Not, In } from 'typeorm'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	desc: { | ||||
|  | @ -25,8 +26,13 @@ export const meta = { | |||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps, me) => { | ||||
| 	const ownedGroups = await UserGroups.find({ | ||||
| 		userId: me.id, | ||||
| 	}); | ||||
| 
 | ||||
| 	const joinings = await UserGroupJoinings.find({ | ||||
| 		userId: me.id, | ||||
| 		userGroupId: Not(In(ownedGroups.map(x => x.id))) | ||||
| 	}); | ||||
| 
 | ||||
| 	return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId))); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue