From c811f3a9b9243e60310f4272f7dbd725552b35d7 Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Wed, 23 Jul 2025 18:40:15 +0100 Subject: [PATCH 1/7] add check to prevent creating too many dialog announcements --- .../backend/src/core/AnnouncementService.ts | 11 +++++ .../endpoints/admin/announcements/create.ts | 48 ++++++++++++------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 95899c2ccc..37c0a8d52b 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -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, 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, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 8da39810e9..3052fdf2fc 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -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 { // 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; + } }); } } From ec629532248652604ab425e54d8a8b80fe87493f Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Wed, 23 Jul 2025 20:46:13 +0100 Subject: [PATCH 2/7] throw error when creating too many dialog announcements by updating one --- .../backend/src/core/AnnouncementService.ts | 11 +++++ .../endpoints/admin/announcements/create.ts | 4 +- .../endpoints/admin/announcements/update.ts | 41 ++++++++++++------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 37c0a8d52b..ebda42369e 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -131,6 +131,17 @@ export class AnnouncementService { @bindThis public async update(announcement: MiAnnouncement, values: Partial, moderator?: MiUser): Promise { + // Check if this operation would produce an active dialog announcement + if ((values.display ?? announcement.display) === 'dialog' && (values.isActive ?? announcement.isActive)) { + // 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.'); + } + } + await this.announcementsRepository.update(announcement.id, { updatedAt: new Date(), title: values.title, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 3052fdf2fc..f17330a58f 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -52,7 +52,7 @@ export const meta = { }, errors: { - tooManyDialogs: { + dialogLimitExceeded: { message: 'Cannot create the announcement because there are too many active dialog-style announcements.', code: 'DIALOG_LIMIT_EXCEEDED', id: '7c1bc084-9c14-4bcf-a910-978cd8e99b5a', @@ -101,7 +101,7 @@ export default class extends Endpoint { // eslint- return packed; } catch (e) { if (e instanceof IdentifiableError) { - if (e.id === 'c0d15f15-f18e-4a40-bcb1-f310d58204ee') throw new ApiError(meta.errors.tooManyDialogs); + if (e.id === 'c0d15f15-f18e-4a40-bcb1-f310d58204ee') throw new ApiError(meta.errors.dialogLimitExceeded); } throw e; } diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index e68a0439c1..8c3475d977 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AnnouncementsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { AnnouncementService } from '@/core/AnnouncementService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -23,6 +24,11 @@ export const meta = { code: 'NO_SUCH_ANNOUNCEMENT', id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc', }, + dialogLimitExceeded: { + message: 'Cannot create the announcement because there are too many active dialog-style announcements.', + code: 'DIALOG_LIMIT_EXCEEDED', + id: '1a5db7ca-6d3f-44bc-ac51-05cae93b643c', + }, }, } as const; @@ -57,20 +63,27 @@ export default class extends Endpoint { // eslint- if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); - await this.announcementService.update(announcement, { - updatedAt: new Date(), - title: ps.title, - text: ps.text, - /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */ - imageUrl: ps.imageUrl || null, - display: ps.display, - icon: ps.icon, - forExistingUsers: ps.forExistingUsers, - silence: ps.silence, - needConfirmationToRead: ps.needConfirmationToRead, - confetti: ps.confetti, - isActive: ps.isActive, - }, me); + try { + await this.announcementService.update(announcement, { + updatedAt: new Date(), + title: ps.title, + text: ps.text, + /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */ + imageUrl: ps.imageUrl || null, + display: ps.display, + icon: ps.icon, + forExistingUsers: ps.forExistingUsers, + silence: ps.silence, + needConfirmationToRead: ps.needConfirmationToRead, + confetti: ps.confetti, + isActive: ps.isActive, + }, me); + } catch (e) { + if (e instanceof IdentifiableError) { + if (e.id === 'c0d15f15-f18e-4a40-bcb1-f310d58204ee') throw new ApiError(meta.errors.dialogLimitExceeded); + } + throw e; + } }); } } From 47f44a0d4c494a3c624d6c62e8c015cf97422d2b Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Wed, 23 Jul 2025 20:49:51 +0100 Subject: [PATCH 3/7] fix not being able to create any dialog announcements --- packages/backend/src/core/AnnouncementService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index ebda42369e..c78ecb7a8f 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -72,6 +72,7 @@ export class AnnouncementService { // Check how many active dialog queries already exist, to enforce a limit const query = this.announcementsRepository.createQueryBuilder('announcement'); query.andWhere('announcement.isActive = true'); + query.andWhere('announcement.display = \'dialog\''); const count = await query.getCount(); if (count >= 5) { throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); @@ -136,6 +137,7 @@ export class AnnouncementService { // Check how many active dialog queries already exist, to enforce a limit const query = this.announcementsRepository.createQueryBuilder('announcement'); query.andWhere('announcement.isActive = true'); + query.andWhere('announcement.display = \'dialog\''); const count = await query.getCount(); if (count >= 5) { throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); From e5aabb29785ff795de196c546d9ef58b91f49d0e Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Wed, 23 Jul 2025 20:56:50 +0100 Subject: [PATCH 4/7] simplify dialog announcement check --- .../backend/src/core/AnnouncementService.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index c78ecb7a8f..e66d85bac7 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -70,11 +70,11 @@ export class AnnouncementService { public async create(values: Partial, 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'); - query.andWhere('announcement.display = \'dialog\''); - const count = await query.getCount(); - if (count >= 5) { + const dialogCount = await this.announcementsRepository.createQueryBuilder('announcement') + .where({ isActive: true }) + .where({ display: 'dialog' }) + .getCount(); + if (dialogCount >= 5) { throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); } } @@ -135,11 +135,11 @@ export class AnnouncementService { // Check if this operation would produce an active dialog announcement if ((values.display ?? announcement.display) === 'dialog' && (values.isActive ?? announcement.isActive)) { // Check how many active dialog queries already exist, to enforce a limit - const query = this.announcementsRepository.createQueryBuilder('announcement'); - query.andWhere('announcement.isActive = true'); - query.andWhere('announcement.display = \'dialog\''); - const count = await query.getCount(); - if (count >= 5) { + const dialogCount = await this.announcementsRepository.createQueryBuilder('announcement') + .where({ isActive: true }) + .where({ display: 'dialog' }) + .getCount(); + if (dialogCount >= 5) { throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); } } From 4926afc264c88711ec89ab7fb4e8dfa8b8a265e9 Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Wed, 23 Jul 2025 22:45:20 +0100 Subject: [PATCH 5/7] move dialog announcement check into different function --- .../backend/src/core/AnnouncementService.ts | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index e66d85bac7..51ab4881fd 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -69,14 +69,7 @@ export class AnnouncementService { @bindThis public async create(values: Partial, 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 dialogCount = await this.announcementsRepository.createQueryBuilder('announcement') - .where({ isActive: true }) - .where({ display: 'dialog' }) - .getCount(); - if (dialogCount >= 5) { - throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); - } + await this.assertDialogAnnouncenmentsCountLimit(); } const announcement = await this.announcementsRepository.insertOne({ @@ -134,14 +127,7 @@ export class AnnouncementService { public async update(announcement: MiAnnouncement, values: Partial, moderator?: MiUser): Promise { // Check if this operation would produce an active dialog announcement if ((values.display ?? announcement.display) === 'dialog' && (values.isActive ?? announcement.isActive)) { - // Check how many active dialog queries already exist, to enforce a limit - const dialogCount = await this.announcementsRepository.createQueryBuilder('announcement') - .where({ isActive: true }) - .where({ display: 'dialog' }) - .getCount(); - if (dialogCount >= 5) { - throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); - } + await this.assertDialogAnnouncenmentsCountLimit(); } await this.announcementsRepository.update(announcement.id, { @@ -246,4 +232,17 @@ export class AnnouncementService { this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements'); } } + + private async assertDialogAnnouncenmentsCountLimit(): Promise { + // Check how many active dialog queries already exist, to enforce a limit + const dialogCount = await this.announcementsRepository.createQueryBuilder('announcement') + .where({ + isActive: true, + display: 'dialog', + }) + .getCount(); + if (dialogCount >= 5) { + throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); + } + } } From 5364e8da26d015d258e106f8f565d45e92cbd80f Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Thu, 28 Aug 2025 21:12:03 +0100 Subject: [PATCH 6/7] make dialog announcement cap configurable --- .config/cypress-devcontainer.yml | 2 ++ .config/docker_example.yml | 2 ++ .config/example.yml | 2 ++ packages/backend/src/config.ts | 5 ++++- packages/backend/src/core/AnnouncementService.ts | 6 +++++- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index 97263da68f..eae7e14308 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -250,6 +250,8 @@ id: 'aidx' #maxAltTextLength: 20000 # Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1) #maxRemoteAltTextLength: 100000 +# Amount of dialog-style announcements that can be active at a time. (minimum: 1) +#maxDialogAnnouncements: 5 # Proxy for HTTP/HTTPS #proxy: http://127.0.0.1:3128 diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 9b57eec88b..de43d3e43c 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -345,6 +345,8 @@ id: 'aidx' #maxBioLength: 1500 # Amount of characters that will be saved for remote user bios. Longer descriptions will be truncated to this length. (minimum: 1) #maxRemoteBioLength: 15000 +# Amount of dialog-style announcements that can be active at a time. (minimum: 1) +#maxDialogAnnouncements: 5 # Proxy for HTTP/HTTPS #proxy: http://127.0.0.1:3128 diff --git a/.config/example.yml b/.config/example.yml index 46ace4abc2..745004305d 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -348,6 +348,8 @@ id: 'aidx' #maxBioLength: 1500 # Amount of characters that will be saved for remote user bios. Longer descriptions will be truncated to this length. (minimum: 1) #maxRemoteBioLength: 15000 +# Amount of dialog-style announcements that can be active at a time. (minimum: 1) +#maxDialogAnnouncements: 5 # Proxy for HTTP/HTTPS #proxy: http://127.0.0.1:3128 diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 21396a1f7f..9d0736d94e 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -98,6 +98,7 @@ type Source = { maxRemoteAltTextLength?: number; maxBioLength?: number; maxRemoteBioLength?: number; + maxDialogAnnouncements?: number; clusterLimit?: number; @@ -265,6 +266,7 @@ export type Config = { maxRemoteAltTextLength: number; maxBioLength: number; maxRemoteBioLength: number; + maxDialogAnnouncements: number; clusterLimit: number | undefined; id: string; outgoingAddress: string | undefined; @@ -467,6 +469,7 @@ export function loadConfig(): Config { maxRemoteAltTextLength: config.maxRemoteAltTextLength ?? 100000, maxBioLength: config.maxBioLength ?? 1500, maxRemoteBioLength: config.maxRemoteBioLength ?? 15000, + maxDialogAnnouncements: config.maxDialogAnnouncements ?? 5, clusterLimit: config.clusterLimit, outgoingAddress: config.outgoingAddress, outgoingAddressFamily: config.outgoingAddressFamily, @@ -664,7 +667,7 @@ function applyEnvOverrides(config: Source) { _apply_top(['sentryForFrontend', 'browserTracingIntegration', 'routeLabel']); _apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]); _apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaDirectory', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]); - _apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'maxBioLength', 'maxRemoteBioLength', 'pidFile', 'filePermissionBits']]); + _apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'maxBioLength', 'maxRemoteBioLength', 'maxDialogAnnouncements', 'pidFile', 'filePermissionBits']]); _apply_top(['import', ['downloadTimeout', 'maxFileSize']]); _apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature', 'setupPassword', 'disallowExternalApRedirect']]); _apply_top(['logging', 'sql', ['disableQueryTruncation', 'enableQueryParamLogging']]); diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 51ab4881fd..0959cd5245 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -15,10 +15,14 @@ import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntitySer import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type { Config } from '@/config.js'; @Injectable() export class AnnouncementService { constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, @@ -241,7 +245,7 @@ export class AnnouncementService { display: 'dialog', }) .getCount(); - if (dialogCount >= 5) { + if (dialogCount >= this.config.maxDialogAnnouncements) { throw new IdentifiableError('c0d15f15-f18e-4a40-bcb1-f310d58204ee', 'Too many dialog announcements.'); } } From 32d45883d64ad37530dbece7a4f2ff432a5f6838 Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Thu, 4 Sep 2025 16:33:30 +0100 Subject: [PATCH 7/7] fix typo in assertDialogAnnouncementsCountLimit method name --- packages/backend/src/core/AnnouncementService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 0959cd5245..2eec484326 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -73,7 +73,7 @@ export class AnnouncementService { @bindThis public async create(values: Partial, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> { if (values.display === 'dialog') { - await this.assertDialogAnnouncenmentsCountLimit(); + await this.assertDialogAnnouncementsCountLimit(); } const announcement = await this.announcementsRepository.insertOne({ @@ -131,7 +131,7 @@ export class AnnouncementService { public async update(announcement: MiAnnouncement, values: Partial, moderator?: MiUser): Promise { // Check if this operation would produce an active dialog announcement if ((values.display ?? announcement.display) === 'dialog' && (values.isActive ?? announcement.isActive)) { - await this.assertDialogAnnouncenmentsCountLimit(); + await this.assertDialogAnnouncementsCountLimit(); } await this.announcementsRepository.update(announcement.id, { @@ -237,7 +237,7 @@ export class AnnouncementService { } } - private async assertDialogAnnouncenmentsCountLimit(): Promise { + private async assertDialogAnnouncementsCountLimit(): Promise { // Check how many active dialog queries already exist, to enforce a limit const dialogCount = await this.announcementsRepository.createQueryBuilder('announcement') .where({