enhance: クリップおよびクリップ内のノートの作成可能数を設定可能に
This commit is contained in:
		
							parent
							
								
									46d2a8726e
								
							
						
					
					
						commit
						c2009acb2d
					
				
					 7 changed files with 90 additions and 0 deletions
				
			
		|  | @ -75,6 +75,7 @@ You should also include the user name that made the change. | ||||||
| - Add Cloudflare Turnstile CAPTCHA support @CyberRex0 | - Add Cloudflare Turnstile CAPTCHA support @CyberRex0 | ||||||
| - 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo | - 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo | ||||||
| - 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo | - 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo | ||||||
|  | - クリップおよびクリップ内のノートの作成可能数を設定可能に @syuilo | ||||||
| - ハードワードミュートの最大文字数を設定可能に @syuilo | - ハードワードミュートの最大文字数を設定可能に @syuilo | ||||||
| - Webhookの作成可能数を設定可能に @syuilo | - Webhookの作成可能数を設定可能に @syuilo | ||||||
| - Server: signToActivityPubGet is set to true by default @syuilo | - Server: signToActivityPubGet is set to true by default @syuilo | ||||||
|  |  | ||||||
|  | @ -964,6 +964,8 @@ _role: | ||||||
|     antennaMax: "アンテナの作成可能数" |     antennaMax: "アンテナの作成可能数" | ||||||
|     wordMuteMax: "ワードミュートの最大文字数" |     wordMuteMax: "ワードミュートの最大文字数" | ||||||
|     webhookMax: "Webhookの作成可能数" |     webhookMax: "Webhookの作成可能数" | ||||||
|  |     clipMax: "クリップの作成可能数" | ||||||
|  |     noteEachClipsMax: "クリップ内のノートの最大数" | ||||||
|   _condition: |   _condition: | ||||||
|     isLocal: "ローカルユーザー" |     isLocal: "ローカルユーザー" | ||||||
|     isRemote: "リモートユーザー" |     isRemote: "リモートユーザー" | ||||||
|  |  | ||||||
|  | @ -23,6 +23,8 @@ export type RoleOptions = { | ||||||
| 	antennaLimit: number; | 	antennaLimit: number; | ||||||
| 	wordMuteLimit: number; | 	wordMuteLimit: number; | ||||||
| 	webhookLimit: number; | 	webhookLimit: number; | ||||||
|  | 	clipLimit: number; | ||||||
|  | 	noteEachClipsLimit: number; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const DEFAULT_ROLE: RoleOptions = { | export const DEFAULT_ROLE: RoleOptions = { | ||||||
|  | @ -35,6 +37,8 @@ export const DEFAULT_ROLE: RoleOptions = { | ||||||
| 	antennaLimit: 5, | 	antennaLimit: 5, | ||||||
| 	wordMuteLimit: 200, | 	wordMuteLimit: 200, | ||||||
| 	webhookLimit: 3, | 	webhookLimit: 3, | ||||||
|  | 	clipLimit: 10, | ||||||
|  | 	noteEachClipsLimit: 200, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
|  | @ -206,6 +210,8 @@ export class RoleService implements OnApplicationShutdown { | ||||||
| 			antennaLimit: Math.max(...getOptionValues('antennaLimit')), | 			antennaLimit: Math.max(...getOptionValues('antennaLimit')), | ||||||
| 			wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')), | 			wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')), | ||||||
| 			webhookLimit: Math.max(...getOptionValues('webhookLimit')), | 			webhookLimit: Math.max(...getOptionValues('webhookLimit')), | ||||||
|  | 			clipLimit: Math.max(...getOptionValues('clipLimit')), | ||||||
|  | 			noteEachClipsLimit: Math.max(...getOptionValues('noteEachClipsLimit')), | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import { IdService } from '@/core/IdService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; | import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; | ||||||
| import { GetterService } from '@/server/api/GetterService.js'; | import { GetterService } from '@/server/api/GetterService.js'; | ||||||
|  | import { RoleService } from '@/core/RoleService.js'; | ||||||
| import { ApiError } from '../../error.js'; | import { ApiError } from '../../error.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
|  | @ -37,6 +38,12 @@ export const meta = { | ||||||
| 			code: 'ALREADY_CLIPPED', | 			code: 'ALREADY_CLIPPED', | ||||||
| 			id: '734806c4-542c-463a-9311-15c512803965', | 			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; | } as const; | ||||||
| 
 | 
 | ||||||
|  | @ -60,6 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 		private clipNotesRepository: ClipNotesRepository, | 		private clipNotesRepository: ClipNotesRepository, | ||||||
| 
 | 
 | ||||||
| 		private idService: IdService, | 		private idService: IdService, | ||||||
|  | 		private roleService: RoleService, | ||||||
| 		private getterService: GetterService, | 		private getterService: GetterService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
|  | @ -86,6 +94,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 				throw new ApiError(meta.errors.alreadyClipped); | 				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({ | 			await this.clipNotesRepository.insert({ | ||||||
| 				id: this.idService.genId(), | 				id: this.idService.genId(), | ||||||
| 				noteId: note.id, | 				noteId: note.id, | ||||||
|  |  | ||||||
|  | @ -4,6 +4,8 @@ import { IdService } from '@/core/IdService.js'; | ||||||
| import type { ClipsRepository } from '@/models/index.js'; | import type { ClipsRepository } from '@/models/index.js'; | ||||||
| import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; | import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { RoleService } from '@/core/RoleService.js'; | ||||||
|  | import { ApiError } from '@/server/api/error.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['clips'], | 	tags: ['clips'], | ||||||
|  | @ -17,6 +19,14 @@ export const meta = { | ||||||
| 		optional: false, nullable: false, | 		optional: false, nullable: false, | ||||||
| 		ref: 'Clip', | 		ref: 'Clip', | ||||||
| 	}, | 	}, | ||||||
|  | 
 | ||||||
|  | 	errors: { | ||||||
|  | 		tooManyClips: { | ||||||
|  | 			message: 'You cannot create clip any more.', | ||||||
|  | 			code: 'TOO_MANY_CLIPS', | ||||||
|  | 			id: '920f7c2d-6208-4b76-8082-e632020f5883', | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  | @ -37,9 +47,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 		private clipsRepository: ClipsRepository, | 		private clipsRepository: ClipsRepository, | ||||||
| 
 | 
 | ||||||
| 		private clipEntityService: ClipEntityService, | 		private clipEntityService: ClipEntityService, | ||||||
|  | 		private roleService: RoleService, | ||||||
| 		private idService: IdService, | 		private idService: IdService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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({ | 			const clip = await this.clipsRepository.insert({ | ||||||
| 				id: this.idService.genId(), | 				id: this.idService.genId(), | ||||||
| 				createdAt: new Date(), | 				createdAt: new Date(), | ||||||
|  |  | ||||||
|  | @ -152,6 +152,30 @@ | ||||||
| 					</MkInput> | 					</MkInput> | ||||||
| 				</div> | 				</div> | ||||||
| 			</MkFolder> | 			</MkFolder> | ||||||
|  | 
 | ||||||
|  | 			<MkFolder> | ||||||
|  | 				<template #label>{{ i18n.ts._role._options.clipMax }}</template> | ||||||
|  | 				<template #suffix>{{ options_clipLimit_useDefault ? i18n.ts._role.useBaseValue : (options_clipLimit_value) }}</template> | ||||||
|  | 				<div class="_gaps"> | ||||||
|  | 					<MkSwitch v-model="options_clipLimit_useDefault" :readonly="readonly"> | ||||||
|  | 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||||
|  | 					</MkSwitch> | ||||||
|  | 					<MkInput v-model="options_clipLimit_value" :disabled="options_clipLimit_useDefault" type="number" :readonly="readonly"> | ||||||
|  | 					</MkInput> | ||||||
|  | 				</div> | ||||||
|  | 			</MkFolder> | ||||||
|  | 
 | ||||||
|  | 			<MkFolder> | ||||||
|  | 				<template #label>{{ i18n.ts._role._options.noteEachClipsMax }}</template> | ||||||
|  | 				<template #suffix>{{ options_noteEachClipsLimit_useDefault ? i18n.ts._role.useBaseValue : (options_noteEachClipsLimit_value) }}</template> | ||||||
|  | 				<div class="_gaps"> | ||||||
|  | 					<MkSwitch v-model="options_noteEachClipsLimit_useDefault" :readonly="readonly"> | ||||||
|  | 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||||
|  | 					</MkSwitch> | ||||||
|  | 					<MkInput v-model="options_noteEachClipsLimit_value" :disabled="options_noteEachClipsLimit_useDefault" type="number" :readonly="readonly"> | ||||||
|  | 					</MkInput> | ||||||
|  | 				</div> | ||||||
|  | 			</MkFolder> | ||||||
| 		</div> | 		</div> | ||||||
| 	</FormSlot> | 	</FormSlot> | ||||||
| 
 | 
 | ||||||
|  | @ -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_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? 0); | ||||||
| let options_webhookLimit_useDefault = $ref(role?.options?.webhookLimit?.useDefault ?? true); | let options_webhookLimit_useDefault = $ref(role?.options?.webhookLimit?.useDefault ?? true); | ||||||
| let options_webhookLimit_value = $ref(role?.options?.webhookLimit?.value ?? 0); | 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_) { | if (_DEV_) { | ||||||
| 	watch($$(condFormula), () => { | 	watch($$(condFormula), () => { | ||||||
|  | @ -241,6 +269,8 @@ function getOptions() { | ||||||
| 		antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, | 		antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, | ||||||
| 		wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value }, | 		wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value }, | ||||||
| 		webhookLimit: { useDefault: options_webhookLimit_useDefault, value: options_webhookLimit_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 }, | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -78,6 +78,20 @@ | ||||||
| 							</MkInput> | 							</MkInput> | ||||||
| 						</MkFolder> | 						</MkFolder> | ||||||
| 
 | 
 | ||||||
|  | 						<MkFolder> | ||||||
|  | 							<template #label>{{ i18n.ts._role._options.clipMax }}</template> | ||||||
|  | 							<template #suffix>{{ options_clipLimit }}</template> | ||||||
|  | 							<MkInput v-model="options_clipLimit" type="number"> | ||||||
|  | 							</MkInput> | ||||||
|  | 						</MkFolder> | ||||||
|  | 
 | ||||||
|  | 						<MkFolder> | ||||||
|  | 							<template #label>{{ i18n.ts._role._options.noteEachClipsMax }}</template> | ||||||
|  | 							<template #suffix>{{ options_noteEachClipsLimit }}</template> | ||||||
|  | 							<MkInput v-model="options_noteEachClipsLimit" type="number"> | ||||||
|  | 							</MkInput> | ||||||
|  | 						</MkFolder> | ||||||
|  | 
 | ||||||
| 						<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton> | 						<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton> | ||||||
| 					</div> | 					</div> | ||||||
| 				</MkFolder> | 				</MkFolder> | ||||||
|  | @ -119,6 +133,8 @@ let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); | ||||||
| let options_antennaLimit = $ref(instance.baseRole.antennaLimit); | let options_antennaLimit = $ref(instance.baseRole.antennaLimit); | ||||||
| let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); | let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); | ||||||
| let options_webhookLimit = $ref(instance.baseRole.webhookLimit); | let options_webhookLimit = $ref(instance.baseRole.webhookLimit); | ||||||
|  | let options_clipLimit = $ref(instance.baseRole.clipLimit); | ||||||
|  | let options_noteEachClipsLimit = $ref(instance.baseRole.noteEachClipsLimit); | ||||||
| 
 | 
 | ||||||
| async function updateBaseRole() { | async function updateBaseRole() { | ||||||
| 	await os.apiWithDialog('admin/roles/update-default-role-override', { | 	await os.apiWithDialog('admin/roles/update-default-role-override', { | ||||||
|  | @ -132,6 +148,8 @@ async function updateBaseRole() { | ||||||
| 			antennaLimit: options_antennaLimit, | 			antennaLimit: options_antennaLimit, | ||||||
| 			wordMuteLimit: options_wordMuteLimit, | 			wordMuteLimit: options_wordMuteLimit, | ||||||
| 			webhookLimit: options_webhookLimit, | 			webhookLimit: options_webhookLimit, | ||||||
|  | 			clipLimit: options_clipLimit, | ||||||
|  | 			noteEachClipsLimit: options_noteEachClipsLimit, | ||||||
| 		}, | 		}, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue