add check to prevent creating too many dialog announcements

This commit is contained in:
bunnybeam 2025-07-23 18:40:15 +01:00
parent ed68230811
commit c811f3a9b9
No known key found for this signature in database
2 changed files with 43 additions and 16 deletions

View file

@ -14,6 +14,7 @@ import { IdService } from '@/core/IdService.js';
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
@Injectable()
export class AnnouncementService {
@ -67,6 +68,16 @@ export class AnnouncementService {
@bindThis
public async create(values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
if (values.display === 'dialog') {
// Check how many active dialog queries already exist, to enforce a limit
const query = this.announcementsRepository.createQueryBuilder('announcement');
query.andWhere('announcement.isActive = true');
const count = await query.getCount();
if (count >= 5) {
throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.');
}
}
const announcement = await this.announcementsRepository.insertOne({
id: this.idService.gen(),
updatedAt: null,

View file

@ -4,8 +4,10 @@
*/
import { Injectable } from '@nestjs/common';
import { ApiError } from '@/server/api/error.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
export const meta = {
tags: ['admin'],
@ -48,6 +50,14 @@ export const meta = {
},
},
},
errors: {
tooManyDialogs: {
message: 'Cannot create the announcement because there are too many active dialog-style announcements.',
code: 'DIALOG_LIMIT_EXCEEDED',
id: '7c1bc084-9c14-4bcf-a910-978cd8e99b5a',
},
},
} as const;
export const paramDef = {
@ -73,22 +83,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const { raw, packed } = await this.announcementService.create({
updatedAt: null,
title: ps.title,
text: ps.text,
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
imageUrl: ps.imageUrl || null,
icon: ps.icon,
display: ps.display,
forExistingUsers: ps.forExistingUsers,
silence: ps.silence,
needConfirmationToRead: ps.needConfirmationToRead,
confetti: ps.confetti,
userId: ps.userId,
}, me);
return packed;
try {
const { raw, packed } = await this.announcementService.create({
updatedAt: null,
title: ps.title,
text: ps.text,
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
imageUrl: ps.imageUrl || null,
icon: ps.icon,
display: ps.display,
forExistingUsers: ps.forExistingUsers,
silence: ps.silence,
needConfirmationToRead: ps.needConfirmationToRead,
confetti: ps.confetti,
userId: ps.userId,
}, me);
return packed;
} catch (e) {
if (e instanceof IdentifiableError) {
if (e.id === 'c0d15f15-f18e-4a40-bcb1-f310d58204ee') throw new ApiError(meta.errors.tooManyDialogs);
}
throw e;
}
});
}
}