feat: お知らせの確認待機時間・優先順位機能
b7fd6bf33a835fd73c2a86eb007074d3680f6efd によるリワーク This reverts commiteeef3965b7This reverts commit04fefb2056This reverts commit576251200f
This commit is contained in:
		| @@ -4,14 +4,17 @@ | ||||
|  */ | ||||
|  | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import { Brackets } from 'typeorm'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { Brackets, In } from 'typeorm'; | ||||
| import type { AnnouncementReadsRepository, AnnouncementsRepository, UsersRepository } from '@/models/index.js'; | ||||
| import type { User } from '@/models/entities/User.js'; | ||||
| import type { AnnouncementReadsRepository, AnnouncementsRepository, Announcement, AnnouncementRead } from '@/models/index.js'; | ||||
| import { Announcement, AnnouncementRead } from '@/models/index.js'; | ||||
| import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { Packed } from '@/misc/json-schema.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { GlobalEventService } from '@/core/GlobalEventService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { Packed } from '@/misc/json-schema.js'; | ||||
| import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||
|  | ||||
| @Injectable() | ||||
| export class AnnouncementService { | ||||
| @@ -22,63 +25,52 @@ export class AnnouncementService { | ||||
| 		@Inject(DI.announcementReadsRepository) | ||||
| 		private announcementReadsRepository: AnnouncementReadsRepository, | ||||
|  | ||||
| 		@Inject(DI.usersRepository) | ||||
| 		private usersRepository: UsersRepository, | ||||
|  | ||||
| 		private idService: IdService, | ||||
| 		private userEntityService: UserEntityService, | ||||
| 		private announcementEntityService: AnnouncementEntityService, | ||||
| 		private globalEventService: GlobalEventService, | ||||
| 	) { | ||||
| 	} | ||||
| 	) {} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async getReads(userId: User['id']): Promise<AnnouncementRead[]> { | ||||
| 		return this.announcementReadsRepository.findBy({ | ||||
| 			userId: userId, | ||||
| 		}); | ||||
| 	} | ||||
| 	public async create( | ||||
| 		values: Partial<Announcement>, | ||||
| 	): Promise<{ raw: Announcement; packed: Packed<'Announcement'> }> { | ||||
| 		const announcement = await this.announcementsRepository | ||||
| 			.insert({ | ||||
| 				id: this.idService.genId(), | ||||
| 				createdAt: new Date(), | ||||
| 				updatedAt: null, | ||||
| 				title: values.title, | ||||
| 				text: values.text, | ||||
| 				imageUrl: values.imageUrl, | ||||
| 				icon: values.icon, | ||||
| 				display: values.display, | ||||
| 				forExistingUsers: values.forExistingUsers, | ||||
| 				needConfirmationToRead: values.needConfirmationToRead, | ||||
| 				closeDuration: values.closeDuration, | ||||
| 				displayOrder: values.displayOrder, | ||||
| 				userId: values.userId, | ||||
| 			}) | ||||
| 			.then((x) => | ||||
| 				this.announcementsRepository.findOneByOrFail(x.identifiers[0]), | ||||
| 			); | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async getUnreadAnnouncements(user: User): Promise<Announcement[]> { | ||||
| 		const readsQuery = this.announcementReadsRepository.createQueryBuilder('read') | ||||
| 			.select('read.announcementId') | ||||
| 			.where('read.userId = :userId', { userId: user.id }); | ||||
|  | ||||
| 		const q = this.announcementsRepository.createQueryBuilder('announcement') | ||||
| 			.where('announcement.isActive = true') | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb.orWhere('announcement.userId = :userId', { userId: user.id }); | ||||
| 				qb.orWhere('announcement.userId IS NULL'); | ||||
| 			})) | ||||
| 			.andWhere(new Brackets(qb => { | ||||
| 				qb.orWhere('announcement.forExistingUsers = false'); | ||||
| 				qb.orWhere('announcement.createdAt > :createdAt', { createdAt: user.createdAt }); | ||||
| 			})) | ||||
| 			.andWhere(`announcement.id NOT IN (${ readsQuery.getQuery() })`); | ||||
|  | ||||
| 		q.setParameters(readsQuery.getParameters()); | ||||
|  | ||||
| 		return q.getMany(); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async create(values: Partial<Announcement>): Promise<{ raw: Announcement; packed: Packed<'Announcement'> }> { | ||||
| 		const announcement = await this.announcementsRepository.insert({ | ||||
| 			id: this.idService.genId(), | ||||
| 			createdAt: new Date(), | ||||
| 			updatedAt: null, | ||||
| 			title: values.title, | ||||
| 			text: values.text, | ||||
| 			imageUrl: values.imageUrl, | ||||
| 			icon: values.icon, | ||||
| 			display: values.display, | ||||
| 			forExistingUsers: values.forExistingUsers, | ||||
| 			needConfirmationToRead: values.needConfirmationToRead, | ||||
| 			userId: values.userId, | ||||
| 		}).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0])); | ||||
|  | ||||
| 		const packed = (await this.packMany([announcement]))[0]; | ||||
| 		const packed = await this.announcementEntityService.pack( | ||||
| 			announcement, | ||||
| 			null, | ||||
| 		); | ||||
|  | ||||
| 		if (values.userId) { | ||||
| 			this.globalEventService.publishMainStream(values.userId, 'announcementCreated', { | ||||
| 				announcement: packed, | ||||
| 			}); | ||||
| 			this.globalEventService.publishMainStream( | ||||
| 				values.userId, | ||||
| 				'announcementCreated', | ||||
| 				{ | ||||
| 					announcement: packed, | ||||
| 				}, | ||||
| 			); | ||||
| 		} else { | ||||
| 			this.globalEventService.publishBroadcastStream('announcementCreated', { | ||||
| 				announcement: packed, | ||||
| @@ -92,44 +84,271 @@ export class AnnouncementService { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async read(user: User, announcementId: Announcement['id']): Promise<void> { | ||||
| 	public async list( | ||||
| 		userId: User['id'] | null, | ||||
| 		limit: number, | ||||
| 		offset: number, | ||||
| 		moderator: User, | ||||
| 	): Promise<(Announcement & { userInfo: Packed<'UserLite'> | null, readCount: number })[]> { | ||||
| 		const query = this.announcementsRepository.createQueryBuilder('announcement'); | ||||
| 		if (userId) { | ||||
| 			query.andWhere('announcement."userId" = :userId', { userId: userId }); | ||||
| 		} else { | ||||
| 			query.andWhere('announcement."userId" IS NULL'); | ||||
| 		} | ||||
|  | ||||
| 		query.orderBy({ | ||||
| 			'announcement."displayOrder"': 'DESC', | ||||
| 			'announcement."createdAt"': 'DESC', | ||||
| 		}); | ||||
|  | ||||
| 		const announcements = await query | ||||
| 			.limit(limit) | ||||
| 			.offset(offset) | ||||
| 			.getMany(); | ||||
|  | ||||
| 		const reads = new Map<Announcement, number>(); | ||||
|  | ||||
| 		for (const announcement of announcements) { | ||||
| 			reads.set(announcement, await this.announcementReadsRepository.countBy({ | ||||
| 				announcementId: announcement.id, | ||||
| 			})); | ||||
| 		} | ||||
|  | ||||
| 		const users = await this.usersRepository.findBy({ | ||||
| 			id: In(announcements.map(a => a.userId).filter(id => id != null)), | ||||
| 		}); | ||||
| 		const packedUsers = await this.userEntityService.packMany(users, moderator, { | ||||
| 			detail: false, | ||||
| 		}); | ||||
|  | ||||
| 		return announcements.map(announcement => ({ | ||||
| 			...announcement, | ||||
| 			userInfo: packedUsers.find(u => u.id === announcement.userId) ?? null, | ||||
| 			readCount: reads.get(announcement) ?? 0, | ||||
| 		})); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async update( | ||||
| 		announcementId: Announcement['id'], | ||||
| 		values: Partial<Announcement>, | ||||
| 	): Promise<{ raw: Announcement; packed: Packed<'Announcement'> }> { | ||||
| 		const oldAnnouncement = await this.announcementsRepository.findOneByOrFail({ | ||||
| 			id: announcementId, | ||||
| 		}); | ||||
|  | ||||
| 		if (oldAnnouncement.userId && oldAnnouncement.userId !== values.userId) { | ||||
| 			await this.announcementReadsRepository.delete({ | ||||
| 				announcementId: announcementId, | ||||
| 				userId: oldAnnouncement.userId, | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		const announcement = await this.announcementsRepository | ||||
| 			.update(announcementId, { | ||||
| 				updatedAt: new Date(), | ||||
| 				isActive: values.isActive, | ||||
| 				title: values.title, | ||||
| 				text: values.text, | ||||
| 				imageUrl: values.imageUrl !== '' ? values.imageUrl : null, | ||||
| 				icon: values.icon, | ||||
| 				display: values.display, | ||||
| 				forExistingUsers: values.forExistingUsers, | ||||
| 				needConfirmationToRead: values.needConfirmationToRead, | ||||
| 				closeDuration: values.closeDuration, | ||||
| 				displayOrder: values.displayOrder, | ||||
| 				userId: values.userId, | ||||
| 			}) | ||||
| 			.then(() => | ||||
| 				this.announcementsRepository.findOneByOrFail({ id: announcementId }), | ||||
| 			); | ||||
|  | ||||
| 		const packed = await this.announcementEntityService.pack( | ||||
| 			announcement, | ||||
| 			values.userId ? { id: values.userId } : null, | ||||
| 		); | ||||
|  | ||||
| 		if (values.userId) { | ||||
| 			this.globalEventService.publishMainStream( | ||||
| 				values.userId, | ||||
| 				'announcementCreated', | ||||
| 				{ | ||||
| 					announcement: packed, | ||||
| 				}, | ||||
| 			); | ||||
| 		} else { | ||||
| 			this.globalEventService.publishBroadcastStream('announcementCreated', { | ||||
| 				announcement: packed, | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		return { | ||||
| 			raw: announcement, | ||||
| 			packed: packed, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async delete(announcementId: Announcement['id']): Promise<void> { | ||||
| 		await this.announcementReadsRepository.delete({ | ||||
| 			announcementId: announcementId, | ||||
| 		}); | ||||
| 		await this.announcementsRepository.delete({ id: announcementId }); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async getAnnouncements( | ||||
| 		me: User | null, | ||||
| 		limit: number, | ||||
| 		offset: number, | ||||
| 		isActive?: boolean, | ||||
| 	): Promise<Packed<'Announcement'>[]> { | ||||
| 		const query = this.announcementsRepository.createQueryBuilder('announcement'); | ||||
| 		if (me) { | ||||
| 			query.leftJoin( | ||||
| 				AnnouncementRead, | ||||
| 				'read', | ||||
| 				'read."announcementId" = announcement.id AND read."userId" = :userId', | ||||
| 				{ userId: me.id }, | ||||
| 			); | ||||
| 			query.select([ | ||||
| 				'announcement.*', | ||||
| 				'CASE WHEN read.id IS NULL THEN FALSE ELSE TRUE END as "isRead"', | ||||
| 			]); | ||||
| 			query | ||||
| 				.andWhere( | ||||
| 					new Brackets((qb) => { | ||||
| 						qb.orWhere('announcement."userId" = :userId', { userId: me.id }); | ||||
| 						qb.orWhere('announcement."userId" IS NULL'); | ||||
| 					}), | ||||
| 				) | ||||
| 				.andWhere( | ||||
| 					new Brackets((qb) => { | ||||
| 						qb.orWhere('announcement."forExistingUsers" = false'); | ||||
| 						qb.orWhere('announcement."createdAt" > :createdAt', { | ||||
| 							createdAt: me.createdAt, | ||||
| 						}); | ||||
| 					}), | ||||
| 				); | ||||
| 		} else { | ||||
| 			query.select([ | ||||
| 				'announcement.*', | ||||
| 				'NULL as "isRead"', | ||||
| 			]); | ||||
| 			query.andWhere('announcement."userId" IS NULL'); | ||||
| 			query.andWhere('announcement."forExistingUsers" = false'); | ||||
| 		} | ||||
|  | ||||
| 		if (isActive !== undefined) { | ||||
| 			query.andWhere('announcement."isActive" = :isActive', { | ||||
| 				isActive: isActive, | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		query.orderBy({ | ||||
| 			'"isRead"': 'ASC', | ||||
| 			'announcement."displayOrder"': 'DESC', | ||||
| 			'announcement."createdAt"': 'DESC', | ||||
| 		}); | ||||
|  | ||||
| 		return this.announcementEntityService.packMany( | ||||
| 			await query | ||||
| 				.limit(limit) | ||||
| 				.offset(offset) | ||||
| 				.getRawMany<Announcement & { isRead?: boolean | null }>(), | ||||
| 			me, | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async getUnreadAnnouncements(me: User): Promise<Packed<'Announcement'>[]> { | ||||
| 		const query = this.announcementsRepository.createQueryBuilder('announcement'); | ||||
| 		query.leftJoinAndSelect( | ||||
| 			AnnouncementRead, | ||||
| 			'read', | ||||
| 			'read."announcementId" = announcement.id AND read."userId" = :userId', | ||||
| 			{ userId: me.id }, | ||||
| 		); | ||||
| 		query.andWhere('read.id IS NULL'); | ||||
| 		query.andWhere('announcement."isActive" = true'); | ||||
|  | ||||
| 		query | ||||
| 			.andWhere( | ||||
| 				new Brackets((qb) => { | ||||
| 					qb.orWhere('announcement."userId" = :userId', { userId: me.id }); | ||||
| 					qb.orWhere('announcement."userId" IS NULL'); | ||||
| 				}), | ||||
| 			) | ||||
| 			.andWhere( | ||||
| 				new Brackets((qb) => { | ||||
| 					qb.orWhere('announcement."forExistingUsers" = false'); | ||||
| 					qb.orWhere('announcement."createdAt" > :createdAt', { | ||||
| 						createdAt: me.createdAt, | ||||
| 					}); | ||||
| 				}), | ||||
| 			); | ||||
|  | ||||
| 		query.orderBy({ | ||||
| 			'announcement."displayOrder"': 'DESC', | ||||
| 			'announcement."createdAt"': 'DESC', | ||||
| 		}); | ||||
|  | ||||
| 		return this.announcementEntityService.packMany( | ||||
| 			await query.getMany(), | ||||
| 			me, | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async countUnreadAnnouncements(me: User): Promise<number> { | ||||
| 		const query = this.announcementsRepository.createQueryBuilder('announcement'); | ||||
| 		query.leftJoinAndSelect( | ||||
| 			AnnouncementRead, | ||||
| 			'read', | ||||
| 			'read."announcementId" = announcement.id AND read."userId" = :userId', | ||||
| 			{ userId: me.id }, | ||||
| 		); | ||||
| 		query.andWhere('read.id IS NULL'); | ||||
| 		query.andWhere('announcement."isActive" = true'); | ||||
|  | ||||
| 		query | ||||
| 			.andWhere( | ||||
| 				new Brackets((qb) => { | ||||
| 					qb.orWhere('announcement."userId" = :userId', { userId: me.id }); | ||||
| 					qb.orWhere('announcement."userId" IS NULL'); | ||||
| 				}), | ||||
| 			) | ||||
| 			.andWhere( | ||||
| 				new Brackets((qb) => { | ||||
| 					qb.orWhere('announcement."forExistingUsers" = false'); | ||||
| 					qb.orWhere('announcement."createdAt" > :createdAt', { | ||||
| 						createdAt: me.createdAt, | ||||
| 					}); | ||||
| 				}), | ||||
| 			); | ||||
|  | ||||
| 		return query.getCount(); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async markAsRead( | ||||
| 		me: User, | ||||
| 		announcementId: Announcement['id'], | ||||
| 	): Promise<void> { | ||||
| 		try { | ||||
| 			await this.announcementReadsRepository.insert({ | ||||
| 				id: this.idService.genId(), | ||||
| 				createdAt: new Date(), | ||||
| 				announcementId: announcementId, | ||||
| 				userId: user.id, | ||||
| 				userId: me.id, | ||||
| 			}); | ||||
| 		} catch (e) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if ((await this.getUnreadAnnouncements(user)).length === 0) { | ||||
| 			this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements'); | ||||
| 		if ((await this.countUnreadAnnouncements(me)) === 0) { | ||||
| 			this.globalEventService.publishMainStream(me.id, 'readAllAnnouncements'); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async packMany( | ||||
| 		announcements: Announcement[], | ||||
| 		me?: { id: User['id'] } | null | undefined, | ||||
| 		options?: { | ||||
| 			reads?: AnnouncementRead[]; | ||||
| 		}, | ||||
| 	): Promise<Packed<'Announcement'>[]> { | ||||
| 		const reads = me ? (options?.reads ?? await this.getReads(me.id)) : []; | ||||
| 		return announcements.map(announcement => ({ | ||||
| 			id: announcement.id, | ||||
| 			createdAt: announcement.createdAt.toISOString(), | ||||
| 			updatedAt: announcement.updatedAt?.toISOString() ?? null, | ||||
| 			text: announcement.text, | ||||
| 			title: announcement.title, | ||||
| 			imageUrl: announcement.imageUrl, | ||||
| 			icon: announcement.icon, | ||||
| 			display: announcement.display, | ||||
| 			needConfirmationToRead: announcement.needConfirmationToRead, | ||||
| 			forYou: announcement.userId === me?.id, | ||||
| 			isRead: reads.some(read => read.announcementId === announcement.id), | ||||
| 		})); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -72,6 +72,7 @@ import PerUserDriveChart from './chart/charts/per-user-drive.js'; | ||||
| import ApRequestChart from './chart/charts/ap-request.js'; | ||||
| import { ChartManagementService } from './chart/ChartManagementService.js'; | ||||
| import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js'; | ||||
| import { AnnouncementEntityService } from './entities/AnnouncementEntityService.js'; | ||||
| import { AntennaEntityService } from './entities/AntennaEntityService.js'; | ||||
| import { AppEntityService } from './entities/AppEntityService.js'; | ||||
| import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js'; | ||||
| @@ -198,6 +199,7 @@ const $ApRequestChart: Provider = { provide: 'ApRequestChart', useExisting: ApRe | ||||
| const $ChartManagementService: Provider = { provide: 'ChartManagementService', useExisting: ChartManagementService }; | ||||
|  | ||||
| const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService }; | ||||
| const $AnnouncementEntityService: Provider = { provide: 'AnnouncementEntityService', useExisting: AnnouncementEntityService }; | ||||
| const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService }; | ||||
| const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService }; | ||||
| const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService }; | ||||
| @@ -324,6 +326,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting | ||||
| 		ApRequestChart, | ||||
| 		ChartManagementService, | ||||
| 		AbuseUserReportEntityService, | ||||
| 		AnnouncementEntityService, | ||||
| 		AntennaEntityService, | ||||
| 		AppEntityService, | ||||
| 		AuthSessionEntityService, | ||||
| @@ -445,6 +448,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting | ||||
| 		$ApRequestChart, | ||||
| 		$ChartManagementService, | ||||
| 		$AbuseUserReportEntityService, | ||||
| 		$AnnouncementEntityService, | ||||
| 		$AntennaEntityService, | ||||
| 		$AppEntityService, | ||||
| 		$AuthSessionEntityService, | ||||
| @@ -566,6 +570,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting | ||||
| 		ApRequestChart, | ||||
| 		ChartManagementService, | ||||
| 		AbuseUserReportEntityService, | ||||
| 		AnnouncementEntityService, | ||||
| 		AntennaEntityService, | ||||
| 		AppEntityService, | ||||
| 		AuthSessionEntityService, | ||||
| @@ -686,6 +691,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting | ||||
| 		$ApRequestChart, | ||||
| 		$ChartManagementService, | ||||
| 		$AbuseUserReportEntityService, | ||||
| 		$AnnouncementEntityService, | ||||
| 		$AntennaEntityService, | ||||
| 		$AppEntityService, | ||||
| 		$AuthSessionEntityService, | ||||
|   | ||||
| @@ -0,0 +1,71 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and other misskey contributors | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
|  | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import type { | ||||
| 	AnnouncementReadsRepository, | ||||
| 	AnnouncementsRepository, | ||||
| } from '@/models/index.js'; | ||||
| import type { Packed } from '@/misc/json-schema.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { Announcement, User } from '@/models/index.js'; | ||||
|  | ||||
| @Injectable() | ||||
| export class AnnouncementEntityService { | ||||
| 	constructor( | ||||
| 		@Inject(DI.announcementsRepository) | ||||
| 		private announcementsRepository: AnnouncementsRepository, | ||||
|  | ||||
| 		@Inject(DI.announcementReadsRepository) | ||||
| 		private announcementReadsRepository: AnnouncementReadsRepository, | ||||
| 	) { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async pack( | ||||
| 		src: Announcement['id'] | Announcement & { isRead?: boolean | null }, | ||||
| 		me: { id: User['id'] } | null | undefined, | ||||
| 	): Promise<Packed<'Announcement'>> { | ||||
| 		const announcement = typeof src === 'object' | ||||
| 			? src | ||||
| 			: await this.announcementsRepository.findOneByOrFail({ | ||||
| 				id: src, | ||||
| 			}) as Announcement & { isRead?: boolean | null }; | ||||
|  | ||||
| 		if (me && announcement.isRead === undefined) { | ||||
| 			announcement.isRead = await this.announcementReadsRepository.countBy({ | ||||
| 				announcementId: announcement.id, | ||||
| 				userId: me.id, | ||||
| 			}).then(count => count > 0); | ||||
| 		} | ||||
|  | ||||
| 		return { | ||||
| 			id: announcement.id, | ||||
| 			createdAt: announcement.createdAt.toISOString(), | ||||
| 			updatedAt: announcement.updatedAt?.toISOString() ?? null, | ||||
| 			title: announcement.title, | ||||
| 			text: announcement.text, | ||||
| 			imageUrl: announcement.imageUrl, | ||||
| 			icon: announcement.icon, | ||||
| 			display: announcement.display, | ||||
| 			forYou: announcement.userId === me?.id, | ||||
| 			needConfirmationToRead: announcement.needConfirmationToRead, | ||||
| 			closeDuration: announcement.closeDuration, | ||||
| 			displayOrder: announcement.displayOrder, | ||||
| 			isRead: announcement.isRead !== null ? announcement.isRead : undefined, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async packMany( | ||||
| 		announcements: (Announcement['id'] | Announcement & { isRead?: boolean | null } | Announcement)[], | ||||
| 		me: { id: User['id'] } | null | undefined, | ||||
| 	) : Promise<Packed<'Announcement'>[]> { | ||||
| 		return (await Promise.allSettled(announcements.map(x => this.pack(x, me)))) | ||||
| 			.filter(result => result.status === 'fulfilled') | ||||
| 			.map(result => (result as PromiseFulfilledResult<Packed<'Announcement'>>).value); | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 まっちゃとーにゅ
					まっちゃとーにゅ