diff --git a/CHANGELOG.md b/CHANGELOG.md index 48dc6ff92d..2901872465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ You should also include the user name that made the change. - Add Cloudflare Turnstile CAPTCHA support @CyberRex0 - 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @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 f0291db54c..fbb7633a1e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -964,6 +964,8 @@ _role: antennaMax: "アンテナの作成可能数" wordMuteMax: "ワードミュートの最大文字数" webhookMax: "Webhookの作成可能数" + clipMax: "クリップの作成可能数" + noteEachClipsMax: "クリップ内のノートの最大数" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index c639786ec6..39413e2a55 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -23,6 +23,8 @@ export type RoleOptions = { antennaLimit: number; wordMuteLimit: number; webhookLimit: number; + clipLimit: number; + noteEachClipsLimit: number; }; export const DEFAULT_ROLE: RoleOptions = { @@ -35,6 +37,8 @@ export const DEFAULT_ROLE: RoleOptions = { antennaLimit: 5, wordMuteLimit: 200, webhookLimit: 3, + clipLimit: 10, + noteEachClipsLimit: 200, }; @Injectable() @@ -206,6 +210,8 @@ export class RoleService implements OnApplicationShutdown { antennaLimit: Math.max(...getOptionValues('antennaLimit')), wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')), webhookLimit: Math.max(...getOptionValues('webhookLimit')), + clipLimit: Math.max(...getOptionValues('clipLimit')), + noteEachClipsLimit: Math.max(...getOptionValues('noteEachClipsLimit')), }; } 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 c5ac4f22d7..3cf096c242 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -5,6 +5,7 @@ import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -37,6 +38,12 @@ export const meta = { code: 'ALREADY_CLIPPED', id: '734806c4-542c-463a-9311-15c512803965', }, + + tooManyClipNotes: { + message: 'You cannot add notes to the clip any more.', + code: 'TOO_MANY_CLIP_NOTES', + id: 'f0dba960-ff73-4615-8df4-d6ac5d9dc118', + }, }, } as const; @@ -60,6 +67,7 @@ export default class extends Endpoint { private clipNotesRepository: ClipNotesRepository, private idService: IdService, + private roleService: RoleService, private getterService: GetterService, ) { super(meta, paramDef, async (ps, me) => { @@ -86,6 +94,13 @@ export default class extends Endpoint { throw new ApiError(meta.errors.alreadyClipped); } + const currentCount = await this.clipNotesRepository.countBy({ + clipId: clip.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).noteEachClipsLimit) { + throw new ApiError(meta.errors.tooManyClipNotes); + } + await this.clipNotesRepository.insert({ id: this.idService.genId(), noteId: note.id, diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index d300203a21..abc0288c89 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -4,6 +4,8 @@ import { IdService } from '@/core/IdService.js'; import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; +import { ApiError } from '@/server/api/error.js'; export const meta = { tags: ['clips'], @@ -17,6 +19,14 @@ export const meta = { optional: false, nullable: false, ref: 'Clip', }, + + errors: { + tooManyClips: { + message: 'You cannot create clip any more.', + code: 'TOO_MANY_CLIPS', + id: '920f7c2d-6208-4b76-8082-e632020f5883', + }, + }, } as const; export const paramDef = { @@ -37,9 +47,17 @@ export default class extends Endpoint { private clipsRepository: ClipsRepository, private clipEntityService: ClipEntityService, + private roleService: RoleService, private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const currentCount = await this.clipsRepository.countBy({ + userId: me.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).clipLimit) { + throw new ApiError(meta.errors.tooManyClips); + } + const clip = await this.clipsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 9d26707423..2fceaf9ce2 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -152,6 +152,30 @@ + + + + +
+ + + + + +
+
+ + + + +
+ + + + + +
+
@@ -223,6 +247,10 @@ let options_wordMuteLimit_useDefault = $ref(role?.options?.wordMuteLimit?.useDef let options_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? 0); let options_webhookLimit_useDefault = $ref(role?.options?.webhookLimit?.useDefault ?? true); let options_webhookLimit_value = $ref(role?.options?.webhookLimit?.value ?? 0); +let options_clipLimit_useDefault = $ref(role?.options?.clipLimit?.useDefault ?? true); +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); if (_DEV_) { watch($$(condFormula), () => { @@ -241,6 +269,8 @@ function getOptions() { antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value }, 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 }, }; } diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index cde5142a63..13b893ba46 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -78,6 +78,20 @@ + + + + + + + + + + + + + + {{ i18n.ts.save }} @@ -119,6 +133,8 @@ let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); let options_antennaLimit = $ref(instance.baseRole.antennaLimit); 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); async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-role-override', { @@ -132,6 +148,8 @@ async function updateBaseRole() { antennaLimit: options_antennaLimit, wordMuteLimit: options_wordMuteLimit, webhookLimit: options_webhookLimit, + clipLimit: options_clipLimit, + noteEachClipsLimit: options_noteEachClipsLimit, }, }); }