parent
							
								
									037d4b581b
								
							
						
					
					
						commit
						2feef81516
					
				
					 17 changed files with 163 additions and 82 deletions
				
			
		|  | @ -6,6 +6,7 @@ unrekleassaf | ||||||
| ### ✨Improvements | ### ✨Improvements | ||||||
| * タイムラインなどを遡っているときは新しいアイテムが来てもスクロールしないように | * タイムラインなどを遡っているときは新しいアイテムが来てもスクロールしないように | ||||||
| * 表示言語を切り替えられるように | * 表示言語を切り替えられるように | ||||||
|  | * グループに招待されたときの通知を追加 | ||||||
| 
 | 
 | ||||||
| ### 🐛Fixes | ### 🐛Fixes | ||||||
| * リストを追加するとエラーが出る問題を修正 | * リストを追加するとエラーが出る問題を修正 | ||||||
|  |  | ||||||
|  | @ -385,6 +385,7 @@ signinWith: "{x}でログイン" | ||||||
| tapSecurityKey: "セキュリティーキーにタッチ" | tapSecurityKey: "セキュリティーキーにタッチ" | ||||||
| or: "もしくは" | or: "もしくは" | ||||||
| uiLanguage: "UIの表示言語" | uiLanguage: "UIの表示言語" | ||||||
|  | groupInvited: "グループに招待されました" | ||||||
| 
 | 
 | ||||||
| _ago: | _ago: | ||||||
|   unknown: "謎" |   unknown: "謎" | ||||||
|  |  | ||||||
							
								
								
									
										38
									
								
								migration/1581526429287-user-group-invitation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								migration/1581526429287-user-group-invitation.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  | 
 | ||||||
|  | export class userGroupInvitation1581526429287 implements MigrationInterface { | ||||||
|  |     name = 'userGroupInvitation1581526429287' | ||||||
|  | 
 | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`CREATE TABLE "user_group_invitation" ("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_160c63ec02bf23f6a5c5e8140d6" PRIMARY KEY ("id"))`, undefined); | ||||||
|  |         await queryRunner.query(`CREATE INDEX "IDX_bfbc6305547539369fe73eb144" ON "user_group_invitation" ("userId") `, undefined); | ||||||
|  |         await queryRunner.query(`CREATE INDEX "IDX_5cc8c468090e129857e9fecce5" ON "user_group_invitation" ("userGroupId") `, undefined); | ||||||
|  |         await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e9793f65f504e5a31fbaedbf2f" ON "user_group_invitation" ("userId", "userGroupId") `, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "notification" ADD "userGroupInvitationId" character varying(32)`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`, undefined); | ||||||
|  |         await queryRunner.query(`CREATE TYPE "notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited')`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum" USING "type"::"text"::"notification_type_enum"`, undefined); | ||||||
|  |         await queryRunner.query(`DROP TYPE "notification_type_enum_old"`, undefined); | ||||||
|  |         await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS 'The type of the Notification.'`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_bfbc6305547539369fe73eb144a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_5cc8c468090e129857e9fecce5a" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_8fe87814e978053a53b1beb7e98" FOREIGN KEY ("userGroupInvitationId") REFERENCES "user_group_invitation"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_8fe87814e978053a53b1beb7e98"`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_5cc8c468090e129857e9fecce5a"`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_bfbc6305547539369fe73eb144a"`, undefined); | ||||||
|  |         await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS ''`, undefined); | ||||||
|  |         await queryRunner.query(`CREATE TYPE "notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted')`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum_old" USING "type"::"text"::"notification_type_enum_old"`, undefined); | ||||||
|  |         await queryRunner.query(`DROP TYPE "notification_type_enum"`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TYPE "notification_type_enum_old" RENAME TO  "notification_type_enum"`, undefined); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "userGroupInvitationId"`, undefined); | ||||||
|  |         await queryRunner.query(`DROP INDEX "IDX_e9793f65f504e5a31fbaedbf2f"`, undefined); | ||||||
|  |         await queryRunner.query(`DROP INDEX "IDX_5cc8c468090e129857e9fecce5"`, undefined); | ||||||
|  |         await queryRunner.query(`DROP INDEX "IDX_bfbc6305547539369fe73eb144"`, undefined); | ||||||
|  |         await queryRunner.query(`DROP TABLE "user_group_invitation"`, undefined); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 			<fa :icon="faPlus" v-if="notification.type === 'follow'"/> | 			<fa :icon="faPlus" v-if="notification.type === 'follow'"/> | ||||||
| 			<fa :icon="faClock" v-if="notification.type === 'receiveFollowRequest'"/> | 			<fa :icon="faClock" v-if="notification.type === 'receiveFollowRequest'"/> | ||||||
| 			<fa :icon="faCheck" v-if="notification.type === 'followRequestAccepted'"/> | 			<fa :icon="faCheck" v-if="notification.type === 'followRequestAccepted'"/> | ||||||
|  | 			<fa :icon="faIdCardAlt" v-if="notification.type === 'groupInvited'"/> | ||||||
| 			<fa :icon="faRetweet" v-if="notification.type === 'renote'"/> | 			<fa :icon="faRetweet" v-if="notification.type === 'renote'"/> | ||||||
| 			<fa :icon="faReply" v-if="notification.type === 'reply'"/> | 			<fa :icon="faReply" v-if="notification.type === 'reply'"/> | ||||||
| 			<fa :icon="faAt" v-if="notification.type === 'mention'"/> | 			<fa :icon="faAt" v-if="notification.type === 'mention'"/> | ||||||
|  | @ -40,13 +41,14 @@ | ||||||
| 		<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><mk-follow-button :user="notification.user" :full="true"/></div></span> | 		<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><mk-follow-button :user="notification.user" :full="true"/></div></span> | ||||||
| 		<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span> | 		<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span> | ||||||
| 		<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span> | 		<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span> | ||||||
|  | 		<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ $t('groupInvited') }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ $t('reject') }}</button></div></span> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faCheck } from '@fortawesome/free-solid-svg-icons'; | import { faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faCheck } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faClock } from '@fortawesome/free-regular-svg-icons'; | import { faClock } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import getNoteSummary from '../../misc/get-note-summary'; | import getNoteSummary from '../../misc/get-note-summary'; | ||||||
| import XReactionIcon from './reaction-icon.vue'; | import XReactionIcon from './reaction-icon.vue'; | ||||||
|  | @ -78,7 +80,8 @@ export default Vue.extend({ | ||||||
| 		return { | 		return { | ||||||
| 			getNoteSummary, | 			getNoteSummary, | ||||||
| 			followRequestDone: false, | 			followRequestDone: false, | ||||||
| 			faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck | 			groupInviteDone: false, | ||||||
|  | 			faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
|  | @ -90,6 +93,18 @@ export default Vue.extend({ | ||||||
| 			this.followRequestDone = true; | 			this.followRequestDone = true; | ||||||
| 			this.$root.api('following/requests/reject', { userId: this.notification.user.id }); | 			this.$root.api('following/requests/reject', { userId: this.notification.user.id }); | ||||||
| 		}, | 		}, | ||||||
|  | 		acceptGroupInvitation() { | ||||||
|  | 			this.groupInviteDone = true; | ||||||
|  | 			this.$root.api('users/groups/invitations/accept', { invitationId: this.notification.invitation.id }); | ||||||
|  | 			this.$root.dialog({ | ||||||
|  | 				type: 'success', | ||||||
|  | 				iconOnly: true, autoClose: true | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 		rejectGroupInvitation() { | ||||||
|  | 			this.groupInviteDone = true; | ||||||
|  | 			this.$root.api('users/groups/invitations/reject', { invitationId: this.notification.invitation.id }); | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  | @ -149,7 +164,7 @@ export default Vue.extend({ | ||||||
| 				height: 100%; | 				height: 100%; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			&.follow, &.followRequestAccepted, &.receiveFollowRequest { | 			&.follow, &.followRequestAccepted, &.receiveFollowRequest, &.groupInvited { | ||||||
| 				padding: 3px; | 				padding: 3px; | ||||||
| 				background: #36aed2; | 				background: #36aed2; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -17,13 +17,13 @@ | ||||||
| 
 | 
 | ||||||
| 	<mk-container :body-togglable="true"> | 	<mk-container :body-togglable="true"> | ||||||
| 		<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template> | 		<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template> | ||||||
| 		<mk-pagination :pagination="invitePagination" #default="{items}" ref="invites"> | 		<mk-pagination :pagination="invitationPagination" #default="{items}" ref="invitations"> | ||||||
| 			<div class="_frame" v-for="invite in items" :key="invite.id"> | 			<div class="_frame" v-for="invitation in items" :key="invitation.id"> | ||||||
| 				<div class="_title">{{ invite.group.name }}</div> | 				<div class="_title">{{ invitation.group.name }}</div> | ||||||
| 				<div class="_content"><mk-avatars :user-ids="invite.group.userIds"/></div> | 				<div class="_content"><mk-avatars :user-ids="invitation.group.userIds"/></div> | ||||||
| 				<div class="_footer"> | 				<div class="_footer"> | ||||||
| 					<mk-button @click="acceptInvite(invite)" primary inline><fa :icon="faCheck"/> {{ $t('accept') }}</mk-button> | 					<mk-button @click="acceptInvite(invitation)" primary inline><fa :icon="faCheck"/> {{ $t('accept') }}</mk-button> | ||||||
| 					<mk-button @click="rejectInvite(invite)" primary inline><fa :icon="faBan"/> {{ $t('reject') }}</mk-button> | 					<mk-button @click="rejectInvite(invitation)" primary inline><fa :icon="faBan"/> {{ $t('reject') }}</mk-button> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</mk-pagination> | 		</mk-pagination> | ||||||
|  | @ -73,7 +73,7 @@ export default Vue.extend({ | ||||||
| 				endpoint: 'users/groups/joined', | 				endpoint: 'users/groups/joined', | ||||||
| 				limit: 10, | 				limit: 10, | ||||||
| 			}, | 			}, | ||||||
| 			invitePagination: { | 			invitationPagination: { | ||||||
| 				endpoint: 'i/user-group-invites', | 				endpoint: 'i/user-group-invites', | ||||||
| 				limit: 10, | 				limit: 10, | ||||||
| 			}, | 			}, | ||||||
|  | @ -95,23 +95,23 @@ export default Vue.extend({ | ||||||
| 				iconOnly: true, autoClose: true | 				iconOnly: true, autoClose: true | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 		acceptInvite(invite) { | 		acceptInvite(invitation) { | ||||||
| 			this.$root.api('users/groups/invitations/accept', { | 			this.$root.api('users/groups/invitations/accept', { | ||||||
| 				inviteId: invite.id | 				invitationId: invitation.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
| 					type: 'success', | 					type: 'success', | ||||||
| 					iconOnly: true, autoClose: true | 					iconOnly: true, autoClose: true | ||||||
| 				}); | 				}); | ||||||
| 				this.$refs.invites.reload(); | 				this.$refs.invitations.reload(); | ||||||
| 				this.$refs.joined.reload(); | 				this.$refs.joined.reload(); | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 		rejectInvite(invite) { | 		rejectInvite(invitation) { | ||||||
| 			this.$root.api('users/groups/invitations/reject', { | 			this.$root.api('users/groups/invitations/reject', { | ||||||
| 				inviteId: invite.id | 				invitationId: invitation.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				this.$refs.invites.reload(); | 				this.$refs.invitations.reload(); | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -26,7 +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 { UserGroupInvitation } from '../models/entities/user-group-invitation'; | ||||||
| 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'; | ||||||
|  | @ -106,7 +106,7 @@ export const entities = [ | ||||||
| 	UserListJoining, | 	UserListJoining, | ||||||
| 	UserGroup, | 	UserGroup, | ||||||
| 	UserGroupJoining, | 	UserGroupJoining, | ||||||
| 	UserGroupInvite, | 	UserGroupInvitation, | ||||||
| 	UserNotePining, | 	UserNotePining, | ||||||
| 	UserSecurityKey, | 	UserSecurityKey, | ||||||
| 	UsedUsername, | 	UsedUsername, | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import { User } from './user'; | ||||||
| import { id } from '../id'; | import { id } from '../id'; | ||||||
| import { Note } from './note'; | import { Note } from './note'; | ||||||
| import { FollowRequest } from './follow-request'; | import { FollowRequest } from './follow-request'; | ||||||
|  | import { UserGroupInvitation } from './user-group-invitation'; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class Notification { | export class Notification { | ||||||
|  | @ -57,12 +58,13 @@ export class Notification { | ||||||
| 	 * pollVote - (自分または自分がWatchしている)投稿の投票に投票された | 	 * pollVote - (自分または自分がWatchしている)投稿の投票に投票された | ||||||
| 	 * receiveFollowRequest - フォローリクエストされた | 	 * receiveFollowRequest - フォローリクエストされた | ||||||
| 	 * followRequestAccepted - 自分の送ったフォローリクエストが承認された | 	 * followRequestAccepted - 自分の送ったフォローリクエストが承認された | ||||||
|  | 	 * groupInvited - グループに招待された | ||||||
| 	 */ | 	 */ | ||||||
| 	@Column('enum', { | 	@Column('enum', { | ||||||
| 		enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted'], | 		enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited'], | ||||||
| 		comment: 'The type of the Notification.' | 		comment: 'The type of the Notification.' | ||||||
| 	}) | 	}) | ||||||
| 	public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted'; | 	public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited'; | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * 通知が読まれたかどうか | 	 * 通知が読まれたかどうか | ||||||
|  | @ -97,6 +99,18 @@ export class Notification { | ||||||
| 	@JoinColumn() | 	@JoinColumn() | ||||||
| 	public followRequest: FollowRequest | null; | 	public followRequest: FollowRequest | null; | ||||||
| 
 | 
 | ||||||
|  | 	@Column({ | ||||||
|  | 		...id(), | ||||||
|  | 		nullable: true | ||||||
|  | 	}) | ||||||
|  | 	public userGroupInvitationId: UserGroupInvitation['id'] | null; | ||||||
|  | 
 | ||||||
|  | 	@ManyToOne(type => UserGroupInvitation, { | ||||||
|  | 		onDelete: 'CASCADE' | ||||||
|  | 	}) | ||||||
|  | 	@JoinColumn() | ||||||
|  | 	public userGroupInvitation: UserGroupInvitation | null; | ||||||
|  | 
 | ||||||
| 	@Column('varchar', { | 	@Column('varchar', { | ||||||
| 		length: 128, nullable: true | 		length: 128, nullable: true | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | @ -5,12 +5,12 @@ import { id } from '../id'; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| @Index(['userId', 'userGroupId'], { unique: true }) | @Index(['userId', 'userGroupId'], { unique: true }) | ||||||
| export class UserGroupInvite { | export class UserGroupInvitation { | ||||||
| 	@PrimaryColumn(id()) | 	@PrimaryColumn(id()) | ||||||
| 	public id: string; | 	public id: string; | ||||||
| 
 | 
 | ||||||
| 	@Column('timestamp with time zone', { | 	@Column('timestamp with time zone', { | ||||||
| 		comment: 'The created date of the UserGroupInvite.' | 		comment: 'The created date of the UserGroupInvitation.' | ||||||
| 	}) | 	}) | ||||||
| 	public createdAt: Date; | 	public createdAt: Date; | ||||||
| 
 | 
 | ||||||
|  | @ -24,7 +24,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 { UserGroupInvitationRepository } from './repositories/user-group-invitation'; | ||||||
| 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'; | ||||||
|  | @ -71,7 +71,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 UserGroupInvitations = getCustomRepository(UserGroupInvitationRepository); | ||||||
| export const UserNotePinings = getRepository(UserNotePining); | export const UserNotePinings = getRepository(UserNotePining); | ||||||
| export const UsedUsernames = getRepository(UsedUsername); | export const UsedUsernames = getRepository(UsedUsername); | ||||||
| export const Followings = getCustomRepository(FollowingRepository); | export const Followings = getCustomRepository(FollowingRepository); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { EntityRepository, Repository } from 'typeorm'; | import { EntityRepository, Repository } from 'typeorm'; | ||||||
| import { Users, Notes } from '..'; | import { Users, Notes, UserGroupInvitations } from '..'; | ||||||
| import { Notification } from '../entities/notification'; | import { Notification } from '../entities/notification'; | ||||||
| import { ensure } from '../../prelude/ensure'; | import { ensure } from '../../prelude/ensure'; | ||||||
| import { awaitAll } from '../../prelude/await-all'; | import { awaitAll } from '../../prelude/await-all'; | ||||||
|  | @ -39,7 +39,10 @@ export class NotificationRepository extends Repository<Notification> { | ||||||
| 			...(notification.type === 'pollVote' ? { | 			...(notification.type === 'pollVote' ? { | ||||||
| 				note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId), | 				note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId), | ||||||
| 				choice: notification.choice | 				choice: notification.choice | ||||||
| 			} : {}) | 			} : {}), | ||||||
|  | 			...(notification.type === 'groupInvited' ? { | ||||||
|  | 				invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!), | ||||||
|  | 			} : {}), | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								src/models/repositories/user-group-invitation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/models/repositories/user-group-invitation.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | import { EntityRepository, Repository } from 'typeorm'; | ||||||
|  | import { UserGroupInvitation } from '../entities/user-group-invitation'; | ||||||
|  | import { UserGroups } from '..'; | ||||||
|  | import { ensure } from '../../prelude/ensure'; | ||||||
|  | 
 | ||||||
|  | @EntityRepository(UserGroupInvitation) | ||||||
|  | export class UserGroupInvitationRepository extends Repository<UserGroupInvitation> { | ||||||
|  | 	public async pack( | ||||||
|  | 		src: UserGroupInvitation['id'] | UserGroupInvitation, | ||||||
|  | 	) { | ||||||
|  | 		const invitation = typeof src === 'object' ? src : await this.findOne(src).then(ensure); | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			id: invitation.id, | ||||||
|  | 			group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId), | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public packMany( | ||||||
|  | 		invitations: any[], | ||||||
|  | 	) { | ||||||
|  | 		return Promise.all(invitations.map(x => this.pack(x))); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,24 +0,0 @@ | ||||||
| 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))); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import $ from 'cafy'; | import $ from 'cafy'; | ||||||
| import { ID } from '../../../../misc/cafy-id'; | import { ID } from '../../../../misc/cafy-id'; | ||||||
| import define from '../../define'; | import define from '../../define'; | ||||||
| import { UserGroupInvites } from '../../../../models'; | import { UserGroupInvitations } from '../../../../models'; | ||||||
| import { makePaginationQuery } from '../../common/make-pagination-query'; | import { makePaginationQuery } from '../../common/make-pagination-query'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
|  | @ -33,13 +33,13 @@ export const meta = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default define(meta, async (ps, user) => { | export default define(meta, async (ps, user) => { | ||||||
| 	const query = makePaginationQuery(UserGroupInvites.createQueryBuilder('invite'), ps.sinceId, ps.untilId) | 	const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) | ||||||
| 		.andWhere(`invite.userId = :meId`, { meId: user.id }) | 		.andWhere(`invitation.userId = :meId`, { meId: user.id }) | ||||||
| 		.leftJoinAndSelect('invite.userGroup', 'user_group'); | 		.leftJoinAndSelect('invitation.userGroup', 'user_group'); | ||||||
| 
 | 
 | ||||||
| 	const invites = await query | 	const invitations = await query | ||||||
| 		.take(ps.limit!) | 		.take(ps.limit!) | ||||||
| 		.getMany(); | 		.getMany(); | ||||||
| 
 | 
 | ||||||
| 	return await UserGroupInvites.packMany(invites); | 	return await UserGroupInvitations.packMany(invitations); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -2,14 +2,14 @@ import $ from 'cafy'; | ||||||
| import { ID } from '../../../../../../misc/cafy-id'; | import { ID } from '../../../../../../misc/cafy-id'; | ||||||
| import define from '../../../../define'; | import define from '../../../../define'; | ||||||
| import { ApiError } from '../../../../error'; | import { ApiError } from '../../../../error'; | ||||||
| import { UserGroupJoinings, UserGroupInvites } from '../../../../../../models'; | import { UserGroupJoinings, UserGroupInvitations } from '../../../../../../models'; | ||||||
| import { genId } from '../../../../../../misc/gen-id'; | import { genId } from '../../../../../../misc/gen-id'; | ||||||
| import { UserGroupJoining } from '../../../../../../models/entities/user-group-joining'; | import { UserGroupJoining } from '../../../../../../models/entities/user-group-joining'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
| 		'ja-JP': 'ユーザーグループへの招待を承認します。', | 		'ja-JP': 'ユーザーグループへの招待を承認します。', | ||||||
| 		'en-US': 'Accept invite of a user group.' | 		'en-US': 'Accept invitation of a user group.' | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	tags: ['groups', 'users'], | 	tags: ['groups', 'users'], | ||||||
|  | @ -19,11 +19,11 @@ export const meta = { | ||||||
| 	kind: 'write:user-groups', | 	kind: 'write:user-groups', | ||||||
| 
 | 
 | ||||||
| 	params: { | 	params: { | ||||||
| 		inviteId: { | 		invitationId: { | ||||||
| 			validator: $.type(ID), | 			validator: $.type(ID), | ||||||
| 			desc: { | 			desc: { | ||||||
| 				'ja-JP': '招待ID', | 				'ja-JP': '招待ID', | ||||||
| 				'en-US': 'The invite ID' | 				'en-US': 'The invitation ID' | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | @ -39,15 +39,15 @@ export const meta = { | ||||||
| 
 | 
 | ||||||
| export default define(meta, async (ps, user) => { | export default define(meta, async (ps, user) => { | ||||||
| 	// Fetch the invitation
 | 	// Fetch the invitation
 | ||||||
| 	const invite = await UserGroupInvites.findOne({ | 	const invitation = await UserGroupInvitations.findOne({ | ||||||
| 		id: ps.inviteId, | 		id: ps.invitationId, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	if (invite == null) { | 	if (invitation == null) { | ||||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | 		throw new ApiError(meta.errors.noSuchInvitation); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (invite.userId !== user.id) { | 	if (invitation.userId !== user.id) { | ||||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | 		throw new ApiError(meta.errors.noSuchInvitation); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -56,8 +56,8 @@ export default define(meta, async (ps, user) => { | ||||||
| 		id: genId(), | 		id: genId(), | ||||||
| 		createdAt: new Date(), | 		createdAt: new Date(), | ||||||
| 		userId: user.id, | 		userId: user.id, | ||||||
| 		userGroupId: invite.userGroupId | 		userGroupId: invitation.userGroupId | ||||||
| 	} as UserGroupJoining); | 	} as UserGroupJoining); | ||||||
| 
 | 
 | ||||||
| 	UserGroupInvites.delete(invite.id); | 	UserGroupInvitations.delete(invitation.id); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -2,12 +2,12 @@ import $ from 'cafy'; | ||||||
| import { ID } from '../../../../../../misc/cafy-id'; | import { ID } from '../../../../../../misc/cafy-id'; | ||||||
| import define from '../../../../define'; | import define from '../../../../define'; | ||||||
| import { ApiError } from '../../../../error'; | import { ApiError } from '../../../../error'; | ||||||
| import { UserGroupInvites } from '../../../../../../models'; | import { UserGroupInvitations } from '../../../../../../models'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
| 		'ja-JP': 'ユーザーグループへの招待を拒否します。', | 		'ja-JP': 'ユーザーグループへの招待を拒否します。', | ||||||
| 		'en-US': 'Reject invite of a user group.' | 		'en-US': 'Reject invitation of a user group.' | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	tags: ['groups', 'users'], | 	tags: ['groups', 'users'], | ||||||
|  | @ -17,11 +17,11 @@ export const meta = { | ||||||
| 	kind: 'write:user-groups', | 	kind: 'write:user-groups', | ||||||
| 
 | 
 | ||||||
| 	params: { | 	params: { | ||||||
| 		inviteId: { | 		invitationId: { | ||||||
| 			validator: $.type(ID), | 			validator: $.type(ID), | ||||||
| 			desc: { | 			desc: { | ||||||
| 				'ja-JP': '招待ID', | 				'ja-JP': '招待ID', | ||||||
| 				'en-US': 'The invite ID' | 				'en-US': 'The invitation ID' | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | @ -37,17 +37,17 @@ export const meta = { | ||||||
| 
 | 
 | ||||||
| export default define(meta, async (ps, user) => { | export default define(meta, async (ps, user) => { | ||||||
| 	// Fetch the invitation
 | 	// Fetch the invitation
 | ||||||
| 	const invite = await UserGroupInvites.findOne({ | 	const invitation = await UserGroupInvitations.findOne({ | ||||||
| 		id: ps.inviteId, | 		id: ps.invitationId, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	if (invite == null) { | 	if (invitation == null) { | ||||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | 		throw new ApiError(meta.errors.noSuchInvitation); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (invite.userId !== user.id) { | 	if (invitation.userId !== user.id) { | ||||||
| 		throw new ApiError(meta.errors.noSuchInvitation); | 		throw new ApiError(meta.errors.noSuchInvitation); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	await UserGroupInvites.delete(invite.id); | 	await UserGroupInvitations.delete(invitation.id); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -3,9 +3,10 @@ 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, UserGroupInvites } from '../../../../../models'; | import { UserGroups, UserGroupJoinings, UserGroupInvitations } from '../../../../../models'; | ||||||
| import { genId } from '../../../../../misc/gen-id'; | import { genId } from '../../../../../misc/gen-id'; | ||||||
| import { UserGroupInvite } from '../../../../../models/entities/user-group-invite'; | import { UserGroupInvitation } from '../../../../../models/entities/user-group-invitation'; | ||||||
|  | import { createNotification } from '../../../../../services/create-notification'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
|  | @ -86,19 +87,24 @@ export default define(meta, async (ps, me) => { | ||||||
| 		throw new ApiError(meta.errors.alreadyAdded); | 		throw new ApiError(meta.errors.alreadyAdded); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	const invite = await UserGroupInvites.findOne({ | 	const existInvitation = await UserGroupInvitations.findOne({ | ||||||
| 		userGroupId: userGroup.id, | 		userGroupId: userGroup.id, | ||||||
| 		userId: user.id | 		userId: user.id | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	if (invite) { | 	if (existInvitation) { | ||||||
| 		throw new ApiError(meta.errors.alreadyInvited); | 		throw new ApiError(meta.errors.alreadyInvited); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	await UserGroupInvites.save({ | 	const invitation = await UserGroupInvitations.save({ | ||||||
| 		id: genId(), | 		id: genId(), | ||||||
| 		createdAt: new Date(), | 		createdAt: new Date(), | ||||||
| 		userId: user.id, | 		userId: user.id, | ||||||
| 		userGroupId: userGroup.id | 		userGroupId: userGroup.id | ||||||
| 	} as UserGroupInvite); | 	} as UserGroupInvitation); | ||||||
|  | 
 | ||||||
|  | 	// 通知を作成
 | ||||||
|  | 	createNotification(user.id, me.id, 'groupInvited', { | ||||||
|  | 		userGroupInvitationId: invitation.id | ||||||
|  | 	}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -6,16 +6,18 @@ import { User } from '../models/entities/user'; | ||||||
| import { Note } from '../models/entities/note'; | import { Note } from '../models/entities/note'; | ||||||
| import { Notification } from '../models/entities/notification'; | import { Notification } from '../models/entities/notification'; | ||||||
| import { FollowRequest } from '../models/entities/follow-request'; | import { FollowRequest } from '../models/entities/follow-request'; | ||||||
|  | import { UserGroupInvitation } from '../models/entities/user-group-invitation'; | ||||||
| 
 | 
 | ||||||
| export async function createNotification( | export async function createNotification( | ||||||
| 	notifieeId: User['id'], | 	notifieeId: User['id'], | ||||||
| 	notifierId: User['id'], | 	notifierId: User['id'], | ||||||
| 	type: string, | 	type: Notification['type'], | ||||||
| 	content?: { | 	content?: { | ||||||
| 		noteId?: Note['id']; | 		noteId?: Note['id']; | ||||||
| 		reaction?: string; | 		reaction?: string; | ||||||
| 		choice?: number; | 		choice?: number; | ||||||
| 		followRequestId?: FollowRequest['id']; | 		followRequestId?: FollowRequest['id']; | ||||||
|  | 		userGroupInvitationId?: UserGroupInvitation['id']; | ||||||
| 	} | 	} | ||||||
| ) { | ) { | ||||||
| 	if (notifieeId === notifierId) { | 	if (notifieeId === notifierId) { | ||||||
|  | @ -36,6 +38,7 @@ export async function createNotification( | ||||||
| 		if (content.reaction) data.reaction = content.reaction; | 		if (content.reaction) data.reaction = content.reaction; | ||||||
| 		if (content.choice) data.choice = content.choice; | 		if (content.choice) data.choice = content.choice; | ||||||
| 		if (content.followRequestId) data.followRequestId = content.followRequestId; | 		if (content.followRequestId) data.followRequestId = content.followRequestId; | ||||||
|  | 		if (content.userGroupInvitationId) data.userGroupInvitationId = content.userGroupInvitationId; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Create notification
 | 	// Create notification
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue