Feat: ユーザーのアイコン/バナーの変更可否をロールで設定可能に (#14078)
* feat: implement role policy "canUpdateBioMedia" * docs(changelog): update changelog * docs(changelog): update changelog * chore: regenerate misskey-js type definitions * chore: Apply suggestion from code review Co-authored-by: anatawa12 <anatawa12@icloud.com> * chore: fix unnecessarily strict inequality check * chore: policies should be gotten only once --------- Co-authored-by: anatawa12 <anatawa12@icloud.com>
This commit is contained in:
		| @@ -25,7 +25,7 @@ import { UserFollowingService } from '@/core/UserFollowingService.js'; | ||||
| import { AccountUpdateService } from '@/core/AccountUpdateService.js'; | ||||
| import { HashtagService } from '@/core/HashtagService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { RoleService } from '@/core/RoleService.js'; | ||||
| import { RolePolicies, RoleService } from '@/core/RoleService.js'; | ||||
| import { CacheService } from '@/core/CacheService.js'; | ||||
| import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; | ||||
| import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; | ||||
| @@ -256,6 +256,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 			const profileUpdates = {} as Partial<MiUserProfile>; | ||||
|  | ||||
| 			const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); | ||||
| 			let policies: RolePolicies | null = null; | ||||
|  | ||||
| 			if (ps.name !== undefined) { | ||||
| 				if (ps.name === null) { | ||||
| @@ -296,14 +297,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 			} | ||||
|  | ||||
| 			if (ps.mutedWords !== undefined) { | ||||
| 				checkMuteWordCount(ps.mutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit); | ||||
| 				policies ??= await this.roleService.getUserPolicies(user.id); | ||||
| 				checkMuteWordCount(ps.mutedWords, policies.wordMuteLimit); | ||||
| 				validateMuteWordRegex(ps.mutedWords); | ||||
|  | ||||
| 				profileUpdates.mutedWords = ps.mutedWords; | ||||
| 				profileUpdates.enableWordMute = ps.mutedWords.length > 0; | ||||
| 			} | ||||
| 			if (ps.hardMutedWords !== undefined) { | ||||
| 				checkMuteWordCount(ps.hardMutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit); | ||||
| 				policies ??= await this.roleService.getUserPolicies(user.id); | ||||
| 				checkMuteWordCount(ps.hardMutedWords, policies.wordMuteLimit); | ||||
| 				validateMuteWordRegex(ps.hardMutedWords); | ||||
| 				profileUpdates.hardMutedWords = ps.hardMutedWords; | ||||
| 			} | ||||
| @@ -322,13 +325,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 			if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; | ||||
| 			if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; | ||||
| 			if (typeof ps.alwaysMarkNsfw === 'boolean') { | ||||
| 				if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole); | ||||
| 				policies ??= await this.roleService.getUserPolicies(user.id); | ||||
| 				if (policies.alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole); | ||||
| 				profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; | ||||
| 			} | ||||
| 			if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive; | ||||
| 			if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; | ||||
|  | ||||
| 			if (ps.avatarId) { | ||||
| 				policies ??= await this.roleService.getUserPolicies(user.id); | ||||
| 				if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole); | ||||
|  | ||||
| 				const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId }); | ||||
|  | ||||
| 				if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); | ||||
| @@ -344,6 +351,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 			} | ||||
|  | ||||
| 			if (ps.bannerId) { | ||||
| 				policies ??= await this.roleService.getUserPolicies(user.id); | ||||
| 				if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole); | ||||
|  | ||||
| 				const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId }); | ||||
|  | ||||
| 				if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); | ||||
| @@ -359,14 +369,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 			} | ||||
|  | ||||
| 			if (ps.avatarDecorations) { | ||||
| 				policies ??= await this.roleService.getUserPolicies(user.id); | ||||
| 				const decorations = await this.avatarDecorationService.getAll(true); | ||||
| 				const [myRoles, myPolicies] = await Promise.all([this.roleService.getUserRoles(user.id), this.roleService.getUserPolicies(user.id)]); | ||||
| 				const myRoles = await this.roleService.getUserRoles(user.id); | ||||
| 				const allRoles = await this.roleService.getRoles(); | ||||
| 				const decorationIds = decorations | ||||
| 					.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id))) | ||||
| 					.map(d => d.id); | ||||
|  | ||||
| 				if (ps.avatarDecorations.length > myPolicies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole); | ||||
| 				if (ps.avatarDecorations.length > policies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole); | ||||
|  | ||||
| 				updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({ | ||||
| 					id: d.id, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Sayamame-beans
					Sayamame-beans