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: "グループ" |   group: "グループ" | ||||||
|   start-with-user: "ユーザーとトークを開始" |   start-with-user: "ユーザーとトークを開始" | ||||||
|   start-with-group: "グループとトークを開始" |   start-with-group: "グループとトークを開始" | ||||||
|  |   select-group: "グループを選択してください" | ||||||
| 
 | 
 | ||||||
| common/views/components/messaging-room.vue: | common/views/components/messaging-room.vue: | ||||||
|   not-talked-user: "このユーザーとの会話はありません" |   not-talked-user: "このユーザーとの会話はありません" | ||||||
|  | @ -764,7 +765,8 @@ common/views/components/user-group-editor.vue: | ||||||
|   remove-user: "このグループから削除" |   remove-user: "このグループから削除" | ||||||
|   delete-are-you-sure: "グループ「$1」を削除しますか?" |   delete-are-you-sure: "グループ「$1」を削除しますか?" | ||||||
|   deleted: "削除しました" |   deleted: "削除しました" | ||||||
|   add-user: "メンバーを追加" |   invite: "招待" | ||||||
|  |   invited: "招待を送信しました" | ||||||
| 
 | 
 | ||||||
| common/views/components/user-lists.vue: | common/views/components/user-lists.vue: | ||||||
|   user-lists: "リスト" |   user-lists: "リスト" | ||||||
|  | @ -775,6 +777,11 @@ common/views/components/user-groups.vue: | ||||||
|   user-groups: "グループ" |   user-groups: "グループ" | ||||||
|   create-group: "グループを作成" |   create-group: "グループを作成" | ||||||
|   group-name: "グループ名" |   group-name: "グループ名" | ||||||
|  |   owned-groups: "自分のグループ" | ||||||
|  |   joined-groups: "参加しているグループ" | ||||||
|  |   invites: "招待" | ||||||
|  |   accept-invite: "参加" | ||||||
|  |   reject-invite: "拒否" | ||||||
| 
 | 
 | ||||||
| common/views/widgets/broadcast.vue: | common/views/widgets/broadcast.vue: | ||||||
|   fetching: "確認中" |   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); | 			this.navigate(user); | ||||||
| 		}, | 		}, | ||||||
| 		async startGroup() { | 		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({ | 			const { canceled, result: group } = await this.$root.dialog({ | ||||||
| 				type: null, | 				type: null, | ||||||
| 				title: this.$t('select-group'), | 				title: this.$t('select-group'), | ||||||
| 				select: { | 				select: { | ||||||
| 					items: groups.map(group => ({ | 					items: groups1.concat(groups2).map(group => ({ | ||||||
| 						value: group, text: group.name | 						value: group, text: group.name | ||||||
| 					})) | 					})) | ||||||
| 				}, | 				}, | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
| 
 | 
 | ||||||
| 		<section> | 		<section> | ||||||
| 			<ui-margin> | 			<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> | 			</ui-margin> | ||||||
| 			<sequential-entrance animation="entranceFromTop" delay="25"> | 			<sequential-entrance animation="entranceFromTop" delay="25"> | ||||||
| 				<div class="kjlrfbes" v-for="user in users"> | 				<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({ | 			const { result: user } = await this.$root.dialog({ | ||||||
| 				user: { | 				user: { | ||||||
| 					local: true | 					local: true | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
| 			if (user == null) return; | 			if (user == null) return; | ||||||
| 			this.$root.api('users/groups/push', { | 			this.$root.api('users/groups/invite', { | ||||||
| 				groupId: this.group.id, | 				groupId: this.group.id, | ||||||
| 				userId: user.id | 				userId: user.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				this.fetchUsers(); | 				this.$root.dialog({ | ||||||
|  | 					type: 'success', | ||||||
|  | 					text: t | ||||||
|  | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -1,36 +1,70 @@ | ||||||
| <template> | <template> | ||||||
| <ui-container> | <div> | ||||||
| 	<template #header><fa :icon="faUsers"/> {{ $t('user-groups') }}</template> | 	<ui-container> | ||||||
| 	<ui-margin> | 		<template #header><fa :icon="faUsers"/> {{ $t('owned-groups') }}</template> | ||||||
| 		<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"> |  | ||||||
| 		<ui-hr/> |  | ||||||
| 		<ui-margin> | 		<ui-margin> | ||||||
| 			<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link> | 			<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-group') }}</ui-button> | ||||||
| 		</ui-margin> | 		</ui-margin> | ||||||
| 	</div> | 		<div class="hwgkdrbl" v-for="group in ownedGroups" :key="group.id"> | ||||||
| </ui-container> | 			<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> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | 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({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('common/views/components/user-groups.vue'), | 	i18n: i18n('common/views/components/user-groups.vue'), | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			fetching: true, | 			ownedGroups: [], | ||||||
| 			groups: [], | 			joinedGroups: [], | ||||||
| 			faUsers, faPlus | 			invites: [], | ||||||
|  | 			faUsers, faPlus, faCheck, faBan, faEnvelopeOpenText | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		this.$root.api('users/groups/owned').then(groups => { | 		this.$root.api('users/groups/owned').then(groups => { | ||||||
| 			this.fetching = false; | 			this.ownedGroups = groups; | ||||||
| 			this.groups = 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', { | 		this.$emit('init', { | ||||||
|  | @ -45,13 +79,35 @@ export default Vue.extend({ | ||||||
| 				input: true | 				input: true | ||||||
| 			}).then(async ({ canceled, result: name }) => { | 			}).then(async ({ canceled, result: name }) => { | ||||||
| 				if (canceled) return; | 				if (canceled) return; | ||||||
| 				const list = await this.$root.api('users/groups/create', { | 				const group = await this.$root.api('users/groups/create', { | ||||||
| 					name | 					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> | </script> | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ import { UserList } from '../models/entities/user-list'; | ||||||
| import { UserListJoining } from '../models/entities/user-list-joining'; | import { UserListJoining } from '../models/entities/user-list-joining'; | ||||||
| import { UserGroup } from '../models/entities/user-group'; | import { UserGroup } from '../models/entities/user-group'; | ||||||
| import { UserGroupJoining } from '../models/entities/user-group-joining'; | import { UserGroupJoining } from '../models/entities/user-group-joining'; | ||||||
|  | import { UserGroupInvite } from '../models/entities/user-group-invite'; | ||||||
| import { Hashtag } from '../models/entities/hashtag'; | import { Hashtag } from '../models/entities/hashtag'; | ||||||
| import { NoteFavorite } from '../models/entities/note-favorite'; | import { NoteFavorite } from '../models/entities/note-favorite'; | ||||||
| import { AbuseUserReport } from '../models/entities/abuse-user-report'; | import { AbuseUserReport } from '../models/entities/abuse-user-report'; | ||||||
|  | @ -110,6 +111,7 @@ export function initDb(justBorrow = false, sync = false, log = false) { | ||||||
| 			UserListJoining, | 			UserListJoining, | ||||||
| 			UserGroup, | 			UserGroup, | ||||||
| 			UserGroupJoining, | 			UserGroupJoining, | ||||||
|  | 			UserGroupInvite, | ||||||
| 			UserNotePining, | 			UserNotePining, | ||||||
| 			Following, | 			Following, | ||||||
| 			FollowRequest, | 			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'; | import { id } from '../id'; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
|  | @Index(['userId', 'userGroupId'], { unique: true }) | ||||||
| export class UserGroupJoining { | export class UserGroupJoining { | ||||||
| 	@PrimaryColumn(id()) | 	@PrimaryColumn(id()) | ||||||
| 	public id: string; | 	public id: string; | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ import { UserListRepository } from './repositories/user-list'; | ||||||
| import { UserListJoining } from './entities/user-list-joining'; | import { UserListJoining } from './entities/user-list-joining'; | ||||||
| import { UserGroupRepository } from './repositories/user-group'; | import { UserGroupRepository } from './repositories/user-group'; | ||||||
| import { UserGroupJoining } from './entities/user-group-joining'; | import { UserGroupJoining } from './entities/user-group-joining'; | ||||||
|  | import { UserGroupInviteRepository } from './repositories/user-group-invite'; | ||||||
| import { FollowRequestRepository } from './repositories/follow-request'; | import { FollowRequestRepository } from './repositories/follow-request'; | ||||||
| import { MutingRepository } from './repositories/muting'; | import { MutingRepository } from './repositories/muting'; | ||||||
| import { BlockingRepository } from './repositories/blocking'; | import { BlockingRepository } from './repositories/blocking'; | ||||||
|  | @ -56,6 +57,7 @@ export const UserLists = getCustomRepository(UserListRepository); | ||||||
| export const UserListJoinings = getRepository(UserListJoining); | export const UserListJoinings = getRepository(UserListJoining); | ||||||
| export const UserGroups = getCustomRepository(UserGroupRepository); | export const UserGroups = getCustomRepository(UserGroupRepository); | ||||||
| export const UserGroupJoinings = getRepository(UserGroupJoining); | export const UserGroupJoinings = getRepository(UserGroupJoining); | ||||||
|  | export const UserGroupInvites = getCustomRepository(UserGroupInviteRepository); | ||||||
| export const UserNotePinings = getRepository(UserNotePining); | export const UserNotePinings = getRepository(UserNotePining); | ||||||
| export const Followings = getCustomRepository(FollowingRepository); | export const Followings = getCustomRepository(FollowingRepository); | ||||||
| export const FollowRequests = getCustomRepository(FollowRequestRepository); | 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 define from '../../../define'; | ||||||
| import { ApiError } from '../../../error'; | import { ApiError } from '../../../error'; | ||||||
| import { getUser } from '../../../common/getters'; | import { getUser } from '../../../common/getters'; | ||||||
| import { UserGroups, UserGroupJoinings } from '../../../../../models'; | import { UserGroups, UserGroupJoinings, UserGroupInvites } from '../../../../../models'; | ||||||
| import { genId } from '../../../../../misc/gen-id'; | 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 = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
| 		'ja-JP': '指定したユーザーグループに指定したユーザーを追加します。', | 		'ja-JP': '指定したユーザーグループに指定したユーザーを招待します。', | ||||||
| 		'en-US': 'Add a user to a user group.' | 		'en-US': 'Invite a user to a user group.' | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	tags: ['groups', 'users'], | 	tags: ['groups', 'users'], | ||||||
|  | @ -50,6 +50,12 @@ export const meta = { | ||||||
| 			message: 'That user has already been added to that group.', | 			message: 'That user has already been added to that group.', | ||||||
| 			code: 'ALREADY_ADDED', | 			code: 'ALREADY_ADDED', | ||||||
| 			id: '7e35c6a0-39b2-4488-aea6-6ee20bd5da2c' | 			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; | 		throw e; | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	const exist = await UserGroupJoinings.findOne({ | 	const joining = await UserGroupJoinings.findOne({ | ||||||
| 		userGroupId: userGroup.id, | 		userGroupId: userGroup.id, | ||||||
| 		userId: user.id | 		userId: user.id | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	if (exist) { | 	if (joining) { | ||||||
| 		throw new ApiError(meta.errors.alreadyAdded); | 		throw new ApiError(meta.errors.alreadyAdded); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Push the user
 | 	const invite = await UserGroupInvites.findOne({ | ||||||
| 	await UserGroupJoinings.save({ | 		userGroupId: userGroup.id, | ||||||
|  | 		userId: user.id | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	if (invite) { | ||||||
|  | 		throw new ApiError(meta.errors.alreadyInvited); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	await UserGroupInvites.save({ | ||||||
| 		id: genId(), | 		id: genId(), | ||||||
| 		createdAt: new Date(), | 		createdAt: new Date(), | ||||||
| 		userId: user.id, | 		userId: user.id, | ||||||
| 		userGroupId: userGroup.id | 		userGroupId: userGroup.id | ||||||
| 	} as UserGroupJoining); | 	} as UserGroupInvite); | ||||||
| }); | }); | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import define from '../../../define'; | import define from '../../../define'; | ||||||
| import { UserGroups, UserGroupJoinings } from '../../../../../models'; | import { UserGroups, UserGroupJoinings } from '../../../../../models'; | ||||||
| import { types, bool } from '../../../../../misc/schema'; | import { types, bool } from '../../../../../misc/schema'; | ||||||
|  | import { Not, In } from 'typeorm'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
|  | @ -25,8 +26,13 @@ export const meta = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default define(meta, async (ps, me) => { | export default define(meta, async (ps, me) => { | ||||||
|  | 	const ownedGroups = await UserGroups.find({ | ||||||
|  | 		userId: me.id, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	const joinings = await UserGroupJoinings.find({ | 	const joinings = await UserGroupJoinings.find({ | ||||||
| 		userId: me.id, | 		userId: me.id, | ||||||
|  | 		userGroupId: Not(In(ownedGroups.map(x => x.id))) | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId))); | 	return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId))); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue