diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b3260cc7..d679a4c01f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ You should also include the user name that made the change. - 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo - 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo - クリップおよびクリップ内のノートの作成可能数を設定可能に @syuilo +- ユーザーリストおよびユーザーリスト内のユーザーの作成可能数を設定可能に @syuilo - ハードワードミュートの最大文字数を設定可能に @syuilo - Webhookの作成可能数を設定可能に @syuilo - Server: signToActivityPubGet is set to true by default @syuilo diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fbb7633a1e..d4fba36746 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -966,6 +966,8 @@ _role: webhookMax: "Webhookの作成可能数" clipMax: "クリップの作成可能数" noteEachClipsMax: "クリップ内のノートの最大数" + userListMax: "ユーザーリストの作成可能数" + userEachUserListsMax: "ユーザーリスト内のユーザーの最大数" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 39413e2a55..0ddcb44ca1 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -25,6 +25,8 @@ export type RoleOptions = { webhookLimit: number; clipLimit: number; noteEachClipsLimit: number; + userListLimit: number; + userEachUserListsLimit: number; }; export const DEFAULT_ROLE: RoleOptions = { @@ -39,6 +41,8 @@ export const DEFAULT_ROLE: RoleOptions = { webhookLimit: 3, clipLimit: 10, noteEachClipsLimit: 200, + userListLimit: 10, + userEachUserListsLimit: 50, }; @Injectable() @@ -212,6 +216,8 @@ export class RoleService implements OnApplicationShutdown { webhookLimit: Math.max(...getOptionValues('webhookLimit')), clipLimit: Math.max(...getOptionValues('clipLimit')), noteEachClipsLimit: Math.max(...getOptionValues('noteEachClipsLimit')), + userListLimit: Math.max(...getOptionValues('userListLimit')), + userEachUserListsLimit: Math.max(...getOptionValues('userEachUserListsLimit')), }; } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 054387ff8e..18c9787fa8 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; @Injectable() export class UserListService { @@ -23,13 +24,21 @@ export class UserListService { private userEntityService: UserEntityService, private idService: IdService, private userFollowingService: UserFollowingService, + private roleService: RoleService, private globalEventServie: GlobalEventService, private proxyAccountService: ProxyAccountService, ) { } @bindThis - public async push(target: User, list: UserList) { + public async push(target: User, list: UserList, me: User) { + const currentCount = await this.userListJoiningsRepository.countBy({ + userListId: list.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userEachUserListsLimit) { + throw new Error('Too many users'); + } + await this.userListJoiningsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 1bec77b837..a9672250c8 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -10,10 +10,10 @@ import { DownloadService } from '@/core/DownloadService.js'; import { UserListService } from '@/core/UserListService.js'; import { IdService } from '@/core/IdService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ImportUserListsProcessorService { @@ -102,7 +102,7 @@ export class ImportUserListsProcessorService { if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; - this.userListService.push(target, list!); + this.userListService.push(target, list!, user); } catch (e) { this.logger.warn(`Error in line:${linenum} ${e}`); } 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 99f0751ea8..810c9909b2 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -5,6 +5,8 @@ import type { UserList } from '@/models/entities/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['lists'], @@ -20,6 +22,14 @@ export const meta = { optional: false, nullable: false, ref: 'UserList', }, + + errors: { + tooManyUserLists: { + message: 'You cannot create user list any more.', + code: 'TOO_MANY_USERLISTS', + id: '0cf21a28-7715-4f39-a20d-777bfdb8d138', + }, + }, } as const; export const paramDef = { @@ -39,8 +49,16 @@ export default class extends Endpoint { private userListEntityService: UserListEntityService, private idService: IdService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + const currentCount = await this.userListsRepository.countBy({ + userId: me.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userListLimit) { + throw new ApiError(meta.errors.tooManyUserLists); + } + const userList = await this.userListsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), 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 96be7e11e8..3a079ee1ab 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -111,7 +111,7 @@ export default class extends Endpoint { } // Push the user - await this.userListService.push(user, userList); + await this.userListService.push(user, userList, me); }); } } diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 2fceaf9ce2..f836b46202 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -176,6 +176,30 @@ + + + + +
+ + + + + +
+
+ + + + +
+ + + + + +
+
@@ -251,6 +275,10 @@ let options_clipLimit_useDefault = $ref(role?.options?.clipLimit?.useDefault ?? let options_clipLimit_value = $ref(role?.options?.clipLimit?.value ?? 0); let options_noteEachClipsLimit_useDefault = $ref(role?.options?.noteEachClipsLimit?.useDefault ?? true); let options_noteEachClipsLimit_value = $ref(role?.options?.noteEachClipsLimit?.value ?? 0); +let options_userListLimit_useDefault = $ref(role?.options?.userListLimit?.useDefault ?? true); +let options_userListLimit_value = $ref(role?.options?.userListLimit?.value ?? 0); +let options_userEachUserListsLimit_useDefault = $ref(role?.options?.userEachUserListsLimit?.useDefault ?? true); +let options_userEachUserListsLimit_value = $ref(role?.options?.userEachUserListsLimit?.value ?? 0); if (_DEV_) { watch($$(condFormula), () => { @@ -271,6 +299,8 @@ function getOptions() { webhookLimit: { useDefault: options_webhookLimit_useDefault, value: options_webhookLimit_value }, clipLimit: { useDefault: options_clipLimit_useDefault, value: options_clipLimit_value }, noteEachClipsLimit: { useDefault: options_noteEachClipsLimit_useDefault, value: options_noteEachClipsLimit_value }, + userListLimit: { useDefault: options_userListLimit_useDefault, value: options_userListLimit_value }, + userEachUserListsLimit: { useDefault: options_userEachUserListsLimit_useDefault, value: options_userEachUserListsLimit_value }, }; } diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 13b893ba46..7a2f269310 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -92,6 +92,20 @@ + + + + + + + + + + + + + + {{ i18n.ts.save }} @@ -135,6 +149,8 @@ let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); let options_webhookLimit = $ref(instance.baseRole.webhookLimit); let options_clipLimit = $ref(instance.baseRole.clipLimit); let options_noteEachClipsLimit = $ref(instance.baseRole.noteEachClipsLimit); +let options_userListLimit = $ref(instance.baseRole.userListLimit); +let options_userEachUserListsLimit = $ref(instance.baseRole.userEachUserListsLimit); async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-role-override', { @@ -150,6 +166,8 @@ async function updateBaseRole() { webhookLimit: options_webhookLimit, clipLimit: options_clipLimit, noteEachClipsLimit: options_noteEachClipsLimit, + userListLimit: options_userListLimit, + userEachUserListsLimit: options_userEachUserListsLimit, }, }); }