From 2b2975c0ddd09698af75dd4fe0e5eb5247a3f5ef Mon Sep 17 00:00:00 2001 From: kabo2468 <28654659+kabo2468@users.noreply.github.com> Date: Sun, 16 Jun 2024 20:11:25 +0900 Subject: [PATCH] =?UTF-8?q?fix(backend):=20=E3=82=A2=E3=83=B3=E3=83=86?= =?UTF-8?q?=E3=83=8A=E7=AD=89=E3=81=8C=E3=83=9D=E3=83=AA=E3=82=B7=E3=83=BC?= =?UTF-8?q?=E3=81=A7=E5=AE=9A=E3=82=81=E3=82=89=E3=82=8C=E3=81=9F=E4=B8=8A?= =?UTF-8?q?=E9=99=90=E3=82=92=E8=B6=85=E3=81=88=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=80=81=E5=A4=89=E6=9B=B4=E3=82=84=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=8C=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20(MisskeyIO#646)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/ClipService.ts | 61 ++++++++++++++++++- packages/backend/src/core/UserListService.ts | 2 +- .../server/api/endpoints/antennas/create.ts | 2 +- .../server/api/endpoints/antennas/update.ts | 15 +++++ .../server/api/endpoints/clips/add-note.ts | 16 +++++ .../src/server/api/endpoints/clips/create.ts | 8 +++ .../src/server/api/endpoints/clips/update.ts | 16 +++++ .../server/api/endpoints/i/import-antennas.ts | 2 +- .../server/api/endpoints/i/webhooks/create.ts | 2 +- .../server/api/endpoints/i/webhooks/update.ts | 12 ++++ .../users/lists/create-from-public.ts | 20 +++++- .../api/endpoints/users/lists/create.ts | 24 +++++++- .../server/api/endpoints/users/lists/push.ts | 33 ++++++++++ .../users/lists/update-membership.ts | 38 +++++++++++- .../api/endpoints/users/lists/update.ts | 37 ++++++++++- packages/backend/test/e2e/antennas.ts | 3 +- packages/backend/test/e2e/clips.ts | 9 ++- 17 files changed, 281 insertions(+), 19 deletions(-) diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts index bb8be26ce..b69ca8362 100644 --- a/packages/backend/src/core/ClipService.ts +++ b/packages/backend/src/core/ClipService.ts @@ -20,6 +20,8 @@ export class ClipService { public static AlreadyAddedError = class extends Error {}; public static TooManyClipNotesError = class extends Error {}; public static TooManyClipsError = class extends Error {}; + public static ClipLimitExceededError = class extends Error {}; + public static ClipNotesLimitExceededError = class extends Error {}; constructor( @Inject(DI.clipsRepository) @@ -38,13 +40,26 @@ export class ClipService { @bindThis public async create(me: MiLocalUser, name: string, isPublic: boolean, description: string | null): Promise { + const policies = await this.roleService.getUserPolicies(me.id); + const currentCount = await this.clipsRepository.countBy({ userId: me.id, }); - if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) { + if (currentCount >= policies.clipLimit) { throw new ClipService.TooManyClipsError(); } + const currentNoteCounts = await this.clipNotesRepository + .createQueryBuilder('cn') + .select('COUNT(*)') + .innerJoin('cn.clip', 'c') + .where('c.userId = :userId', { userId: me.id }) + .groupBy('cn.clipId') + .getRawMany<{ count: number }>(); + if (currentNoteCounts.some((x) => x.count > policies.noteEachClipsLimit)) { + throw new ClipService.ClipNotesLimitExceededError(); + } + const clip = await this.clipsRepository.insert({ id: this.idService.gen(), userId: me.id, @@ -67,6 +82,26 @@ export class ClipService { throw new ClipService.NoSuchClipError(); } + const policies = await this.roleService.getUserPolicies(me.id); + + const currentCount = await this.clipsRepository.countBy({ + userId: me.id, + }); + if (currentCount > policies.clipLimit) { + throw new ClipService.ClipLimitExceededError(); + } + + const currentNoteCounts = await this.clipNotesRepository + .createQueryBuilder('cn') + .select('COUNT(*)') + .innerJoin('cn.clip', 'c') + .where('c.userId = :userId', { userId: me.id }) + .groupBy('cn.clipId') + .getRawMany<{ count: number }>(); + if (currentNoteCounts.some((x) => x.count > policies.noteEachClipsLimit)) { + throw new ClipService.ClipNotesLimitExceededError(); + } + await this.clipsRepository.update(clip.id, { name: name, description: description, @@ -99,10 +134,30 @@ export class ClipService { throw new ClipService.NoSuchClipError(); } - const currentCount = await this.clipNotesRepository.countBy({ + const policies = await this.roleService.getUserPolicies(me.id); + + const currentClipCount = await this.clipsRepository.countBy({ + userId: me.id, + }); + if (currentClipCount > policies.clipLimit) { + throw new ClipService.ClipLimitExceededError(); + } + + const currentNoteCounts = await this.clipNotesRepository + .createQueryBuilder('cn') + .select('COUNT(*)') + .innerJoin('cn.clip', 'c') + .where('c.userId = :userId', { userId: me.id }) + .groupBy('cn.clipId') + .getRawMany<{ count: number }>(); + if (currentNoteCounts.some((x) => x.count > policies.noteEachClipsLimit)) { + throw new ClipService.ClipNotesLimitExceededError(); + } + + const currentNoteCount = await this.clipNotesRepository.countBy({ clipId: clip.id, }); - if (currentCount > (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) { + if (currentNoteCount >= policies.noteEachClipsLimit) { throw new ClipService.TooManyClipNotesError(); } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index c16af88ce..09a25079d 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -95,7 +95,7 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { const currentCount = await this.userListMembershipsRepository.countBy({ userListId: list.id, }); - if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) { + if (currentCount >= (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) { throw new UserListService.TooManyUsersError(); } diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 1181998a3..8412e1a28 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -95,7 +95,7 @@ export default class extends Endpoint { // eslint- const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id, }); - if (currentAntennasCount > (await this.roleService.getUserPolicies(me.id)).antennaLimit) { + if (currentAntennasCount >= (await this.roleService.getUserPolicies(me.id)).antennaLimit) { throw new ApiError(meta.errors.tooManyAntennas); } diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index d962db317..aa1e7f337 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -9,6 +9,7 @@ import type { AntennasRepository, UserListsRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -33,6 +34,12 @@ export const meta = { code: 'NO_SUCH_USER_LIST', id: '1c6b35c9-943e-48c2-81e4-2844989407f7', }, + + antennaLimitExceeded: { + message: 'You cannot update the antenna because you have exceeded the limit of antennas.', + code: 'ANTENNA_LIMIT_EXCEEDED', + id: '3166a92e-09d9-4c09-afa3-1dbe34a3afcf', + }, }, res: { @@ -83,6 +90,7 @@ export default class extends Endpoint { // eslint- private antennaEntityService: AntennaEntityService, private globalEventService: GlobalEventService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { if (ps.keywords && ps.excludeKeywords) { @@ -100,6 +108,13 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchAntenna); } + const currentAntennasCount = await this.antennasRepository.countBy({ + userId: me.id, + }); + if (currentAntennasCount > (await this.roleService.getUserPolicies(me.id)).antennaLimit) { + throw new ApiError(meta.errors.antennaLimitExceeded); + } + let userList; if ((ps.src === 'list' || antenna.src === 'list') && ps.userListId) { diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 7f055f6e3..12718e2d4 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -48,6 +48,18 @@ export const meta = { code: 'TOO_MANY_CLIP_NOTES', id: 'f0dba960-ff73-4615-8df4-d6ac5d9dc118', }, + + clipLimitExceeded: { + message: 'You cannot add a note to the clip because you have exceeded the limit of clips.', + code: 'CLIP_LIMIT_EXCEEDED', + id: '456cd06d-9f5b-4793-8108-dffe6e257d98', + }, + + clipNotesLimitExceeded: { + message: 'You cannot add a note to the clip because you have exceeded the limit of notes in other clips.', + code: 'CLIP_NOTES_LIMIT_EXCEEDED', + id: 'f3d6de24-ad27-418d-9d13-b50165dbce66', + }, }, } as const; @@ -77,6 +89,10 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyClipped); } else if (e instanceof ClipService.TooManyClipNotesError) { throw new ApiError(meta.errors.tooManyClipNotes); + } else if (e instanceof ClipService.ClipLimitExceededError) { + throw new ApiError(meta.errors.clipLimitExceeded); + } else if (e instanceof ClipService.ClipNotesLimitExceededError) { + throw new ApiError(meta.errors.clipNotesLimitExceeded); } else { throw e; } diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 3e2d46be4..5df457c91 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -32,6 +32,12 @@ export const meta = { code: 'TOO_MANY_CLIPS', id: '920f7c2d-6208-4b76-8082-e632020f5883', }, + + clipNotesLimitExceeded: { + message: 'You cannot create a clip any more because you have exceeded the limit of notes in a clip.', + code: 'CLIP_NOTES_LIMIT_EXCEEDED', + id: '1fdd390f-dcd3-4b65-88d9-6476159bc5c8', + }, }, } as const; @@ -58,6 +64,8 @@ export default class extends Endpoint { // eslint- } catch (e) { if (e instanceof ClipService.TooManyClipsError) { throw new ApiError(meta.errors.tooManyClips); + } else if (e instanceof ClipService.ClipNotesLimitExceededError) { + throw new ApiError(meta.errors.clipNotesLimitExceeded); } throw e; } diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 72d7f3bb5..dc0c996dc 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -25,6 +25,18 @@ export const meta = { code: 'NO_SUCH_CLIP', id: 'b4d92d70-b216-46fa-9a3f-a8c811699257', }, + + clipLimitExceeded: { + message: 'You cannot update the clip because you have exceeded the limit of clips.', + code: 'CLIP_LIMIT_EXCEEDED', + id: 'fed46dd9-d99a-4a88-b23f-8d31c80b5b25', + }, + + clipNotesLimitExceeded: { + message: 'You cannot update the clip because you have exceeded the limit of notes in a clip.', + code: 'CLIP_NOTES_LIMIT_EXCEEDED', + id: '6f02ab37-66a4-4285-afaf-a8b1000e8f3f', + }, }, res: { @@ -58,6 +70,10 @@ export default class extends Endpoint { // eslint- } catch (e) { if (e instanceof ClipService.NoSuchClipError) { throw new ApiError(meta.errors.noSuchClip); + } else if (e instanceof ClipService.ClipLimitExceededError) { + throw new ApiError(meta.errors.clipLimitExceeded); + } else if (e instanceof ClipService.ClipNotesLimitExceededError) { + throw new ApiError(meta.errors.clipNotesLimitExceeded); } throw e; } diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts index eb9dae0a3..a6edeb222 100644 --- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -80,7 +80,7 @@ export default class extends Endpoint { if (file.size === 0) throw new ApiError(meta.errors.emptyFile); const antennas: (_Antenna & { userListAccts: string[] | null })[] = JSON.parse(await this.downloadService.downloadTextFile(file.url)); const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id }); - if (currentAntennasCount + antennas.length > (await this.roleService.getUserPolicies(me.id)).antennaLimit) { + if (currentAntennasCount + antennas.length >= (await this.roleService.getUserPolicies(me.id)).antennaLimit) { throw new ApiError(meta.errors.tooManyAntennas); } this.queueService.createImportAntennasJob(me, antennas); diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 71f9644f6..5a5b9b055 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -91,7 +91,7 @@ export default class extends Endpoint { // eslint- const currentWebhooksCount = await this.webhooksRepository.countBy({ userId: me.id, }); - if (currentWebhooksCount > (await this.roleService.getUserPolicies(me.id)).webhookLimit) { + if (currentWebhooksCount >= (await this.roleService.getUserPolicies(me.id)).webhookLimit) { throw new ApiError(meta.errors.tooManyWebhooks); } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts index 40a2efd4c..a2e80f76a 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -31,6 +31,11 @@ export const meta = { code: 'YOU_ARE_NOT_ADMIN', id: 'a70c7643-1db5-4ebf-becd-ff4b4223cf23', }, + webhookLimitExceeded: { + message: 'You cannot update the webhook because you have exceeded the limit of webhooks.', + code: 'WEBHOOK_LIMIT_EXCEEDED', + id: 'a261cb2d-867d-47a8-a743-8bbd2c1438b1', + }, }, } as const; @@ -62,6 +67,13 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + const currentWebhooksCount = await this.webhooksRepository.countBy({ + userId: me.id, + }); + if (currentWebhooksCount > (await this.roleService.getUserPolicies(me.id)).webhookLimit) { + throw new ApiError(meta.errors.webhookLimitExceeded); + } + const webhook = await this.webhooksRepository.findOneBy({ id: ps.webhookId, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index edc1431d6..394d0fcf7 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -61,6 +61,12 @@ export const meta = { code: 'TOO_MANY_USERS', id: '1845ea77-38d1-426e-8e4e-8b83b24f5bd7', }, + + listUsersLimitExceeded: { + message: 'You cannot create a list because you have exceeded the limit of users in a list.', + code: 'LIST_USERS_LIMIT_EXCEEDED', + id: '3e205e58-0798-40f2-a589-a78a619ee3d4', + }, }, } as const; @@ -99,13 +105,25 @@ export default class extends Endpoint { // eslint- }, }); if (!listExist) throw new ApiError(meta.errors.noSuchList); + + const policies = await this.roleService.getUserPolicies(me.id); const currentCount = await this.userListsRepository.countBy({ userId: me.id, }); - if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) { + if (currentCount >= policies.userListLimit) { throw new ApiError(meta.errors.tooManyUserLists); } + const currentUserCounts = await this.userListMembershipsRepository + .createQueryBuilder('ulm') + .select('COUNT(*)') + .where('ulm.userListUserId = :userId', { userId: me.id }) + .groupBy('ulm.userListId') + .getRawMany<{ count: number }>(); + if (currentUserCounts.some((x) => x.count > policies.userEachUserListsLimit)) { + throw new ApiError(meta.errors.listUsersLimitExceeded); + } + const userList = await this.userListsRepository.insert({ id: this.idService.gen(), userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index bb7017588..ac6129c5b 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/_.js'; +import type { UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import type { MiUserList } from '@/models/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -37,6 +37,12 @@ export const meta = { code: 'TOO_MANY_USERLISTS', id: '0cf21a28-7715-4f39-a20d-777bfdb8d138', }, + + listUsersLimitExceeded: { + message: 'You cannot create a list because you have exceeded the limit of users in a list.', + code: 'LIST_USERS_LIMIT_EXCEEDED', + id: 'af66c10d-b0e6-418c-a205-4dd46a482e30', + }, }, } as const; @@ -54,18 +60,32 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + private userListEntityService: UserListEntityService, private idService: IdService, private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + const policies = await this.roleService.getUserPolicies(me.id); const currentCount = await this.userListsRepository.countBy({ userId: me.id, }); - if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) { + if (currentCount >= policies.userListLimit) { throw new ApiError(meta.errors.tooManyUserLists); } + const currentUserCounts = await this.userListMembershipsRepository + .createQueryBuilder('ulm') + .select('COUNT(*)') + .where('ulm.userListUserId = :userId', { userId: me.id }) + .groupBy('ulm.userListId') + .getRawMany<{ count: number }>(); + if (currentUserCounts.some((x) => x.count > policies.userEachUserListsLimit)) { + throw new ApiError(meta.errors.listUsersLimitExceeded); + } + const userList = await this.userListsRepository.insert({ id: this.idService.gen(), userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index dd188d5e9..4c470787b 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -10,6 +10,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -59,6 +60,18 @@ export const meta = { code: 'TOO_MANY_USERS', id: '2dd9752e-a338-413d-8eec-41814430989b', }, + + listLimitExceeded: { + message: 'You cannot add a user to the list because you have exceeded the limit of lists.', + code: 'LIST_LIMIT_EXCEEDED', + id: '5906ab2d-c164-44bc-a60a-464beba52be9', + }, + + listUsersLimitExceeded: { + message: 'You cannot add a user to the list because you have exceeded the limit of users in other list.', + code: 'LIST_USERS_LIMIT_EXCEEDED', + id: 'd1054b77-908a-4f7d-a9cc-44d665267108', + }, }, } as const; @@ -85,6 +98,7 @@ export default class extends Endpoint { // eslint- private getterService: GetterService, private userListService: UserListService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { // Fetch the list @@ -97,6 +111,25 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchList); } + // Check the list limit + const policies = await this.roleService.getUserPolicies(me.id); + const currentCount = await this.userListsRepository.countBy({ + userId: me.id, + }); + if (currentCount > policies.userListLimit) { + throw new ApiError(meta.errors.listLimitExceeded); + } + + const currentUserCounts = await this.userListMembershipsRepository + .createQueryBuilder('ulm') + .select('COUNT(*)') + .where('ulm.userListUserId = :userId', { userId: me.id }) + .groupBy('ulm.userListId') + .getRawMany<{ count: number }>(); + if (currentUserCounts.some((x) => x.count > policies.userEachUserListsLimit)) { + throw new ApiError(meta.errors.listUsersLimitExceeded); + } + // Fetch the user const user = await this.getterService.getUser(ps.userId).catch(err => { if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); diff --git a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts index 3948ae168..f099be767 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts @@ -4,11 +4,12 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/_.js'; +import type { UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { UserListService } from '@/core/UserListService.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -32,6 +33,18 @@ export const meta = { code: 'NO_SUCH_USER', id: '588e7f72-c744-4a61-b180-d354e912bda2', }, + + listLimitExceeded: { + message: 'You cannot update the user because you have exceeded the limit of lists.', + code: 'LIST_LIMIT_EXCEEDED', + id: 'd4005118-e773-4132-bafb-10ba22c78da3', + }, + + listUsersLimitExceeded: { + message: 'You cannot update the user because you have exceeded the limit of users in a list.', + code: 'LIST_USERS_LIMIT_EXCEEDED', + id: 'db7fe164-73d0-4788-8ed1-b6a19e95990d', + }, }, } as const; @@ -51,8 +64,12 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + private userListService: UserListService, private getterService: GetterService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { // Fetch the list @@ -65,6 +82,25 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchList); } + // Check the list limit + const policies = await this.roleService.getUserPolicies(me.id); + const currentCount = await this.userListsRepository.countBy({ + userId: me.id, + }); + if (currentCount > policies.userListLimit) { + throw new ApiError(meta.errors.listLimitExceeded); + } + + const currentUserCounts = await this.userListMembershipsRepository + .createQueryBuilder('ulm') + .select('COUNT(*)') + .where('ulm.userListUserId = :userId', { userId: me.id }) + .groupBy('ulm.userListId') + .getRawMany<{ count: number }>(); + if (currentUserCounts.some((x) => x.count > policies.userEachUserListsLimit)) { + throw new ApiError(meta.errors.listUsersLimitExceeded); + } + // Fetch the user const user = await this.getterService.getUser(ps.userId).catch(err => { if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index ae0753f2c..173dfa68b 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -4,10 +4,11 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/_.js'; +import type { UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -32,6 +33,18 @@ export const meta = { code: 'NO_SUCH_LIST', id: '796666fe-3dff-4d39-becb-8a5932c1d5b7', }, + + listLimitExceeded: { + message: 'You cannot update the list because you have exceeded the limit of lists.', + code: 'LIST_LIMIT_EXCEEDED', + id: '0a1fa63e-3e4c-4bc2-afd1-1ff853b4560e', + }, + + listUsersLimitExceeded: { + message: 'You cannot update the list because you have exceeded the limit of users in a list.', + code: 'LIST_USERS_LIMIT_EXCEEDED', + id: '831fd3b2-4ac8-421e-89db-bfd98944e529', + }, }, } as const; @@ -51,7 +64,11 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + private userListEntityService: UserListEntityService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { const userList = await this.userListsRepository.findOneBy({ @@ -63,6 +80,24 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchList); } + const policies = await this.roleService.getUserPolicies(me.id); + const currentCount = await this.userListsRepository.countBy({ + userId: me.id, + }); + if (currentCount > policies.userListLimit) { + throw new ApiError(meta.errors.listLimitExceeded); + } + + const currentUserCounts = await this.userListMembershipsRepository + .createQueryBuilder('ulm') + .select('COUNT(*)') + .where('ulm.userListUserId = :userId', { userId: me.id }) + .groupBy('ulm.userListId') + .getRawMany<{ count: number }>(); + if (currentUserCounts.some((x) => x.count > policies.userEachUserListsLimit)) { + throw new ApiError(meta.errors.listUsersLimitExceeded); + } + await this.userListsRepository.update(userList.id, { name: ps.name, isPublic: ps.isPublic, diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 215cf652d..05352916a 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -167,8 +167,7 @@ describe('アンテナ', () => { }); test('が上限いっぱいまで作成できること', async () => { - // antennaLimit + 1まで作れるのがキモ - const response = await Promise.all([...Array(DEFAULT_POLICIES.antennaLimit + 1)].map(() => successfulApiCall({ + const response = await Promise.all([...Array(DEFAULT_POLICIES.antennaLimit)].map(() => successfulApiCall({ endpoint: 'antennas/create', parameters: { ...defaultParam }, user: alice, diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 91d8bfa5d..ed5e3dfa9 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -153,8 +153,7 @@ describe('クリップ', () => { }); test('の作成はポリシーで定められた数以上はできない。', async () => { - // ポリシー + 1まで作れるという所がミソ - const clipLimit = DEFAULT_POLICIES.clipLimit + 1; + const clipLimit = DEFAULT_POLICIES.clipLimit; for (let i = 0; i < clipLimit; i++) { await create(); } @@ -327,10 +326,10 @@ describe('クリップ', () => { }); test('の一覧(clips/list)が取得できる(上限いっぱい)', async () => { - const clipLimit = DEFAULT_POLICIES.clipLimit + 1; + const clipLimit = DEFAULT_POLICIES.clipLimit; const clips = await createMany({}, clipLimit); const res = await list({ - parameters: { limit: 1 }, // FIXME: 無視されて11全部返ってくる + parameters: { limit: 1 }, // FIXME: 無視されて10全部返ってくる }); // 返ってくる配列には順序保障がないのでidでソートして厳密比較 @@ -705,7 +704,7 @@ describe('クリップ', () => { // TODO: 17000msくらいかかる... test('をポリシーで定められた上限いっぱい(200)を超えて追加はできない。', async () => { - const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit + 1; + const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit; const noteList = await Promise.all([...Array(noteLimit)].map((_, i) => post(alice, { text: `test ${i}`, }) as unknown)) as Misskey.entities.Note[];