Skip to content

Commit

Permalink
Feat: ユーザーのアイコン/バナーの変更可否をロールで設定可能に (#207)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

---------

Co-authored-by: anatawa12 <[email protected]>
  • Loading branch information
Sayamame-beans and anatawa12 authored Jul 3, 2024
1 parent d34f224 commit 65f0d53
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### General
- 未知のリモートユーザーによる通知が発生するノートをブロックする機能の改善 (#206)
- Feat: ユーザーのアイコン/バナーの変更可否をロールで設定可能に
- 変更不可となっていても、設定済みのものを解除してデフォルト画像に戻すことは出来ます

### Client

Expand Down
4 changes: 4 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6630,6 +6630,10 @@ export interface Locale extends ILocale {
* ファイルにNSFWを常に付与
*/
"alwaysMarkNsfw": string;
/**
* アイコンとバナーの更新を許可
*/
"canUpdateBioMedia": string;
/**
* ノートのピン留めの最大数
*/
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,7 @@ _role:
canManageAvatarDecorations: "アバターデコレーションの管理"
driveCapacity: "ドライブ容量"
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
canUpdateBioMedia: "アイコンとバナーの更新を許可"
pinMax: "ノートのピン留めの最大数"
antennaMax: "アンテナの作成可能数"
wordMuteMax: "ワードミュートの最大文字数"
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type RolePolicies = {
canHideAds: boolean;
driveCapacityMb: number;
alwaysMarkNsfw: boolean;
canUpdateBioMedia: boolean;
pinLimit: number;
antennaLimit: number;
wordMuteLimit: number;
Expand Down Expand Up @@ -77,6 +78,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canHideAds: false,
driveCapacityMb: 100,
alwaysMarkNsfw: false,
canUpdateBioMedia: true,
pinLimit: 5,
antennaLimit: 5,
wordMuteLimit: 200,
Expand Down Expand Up @@ -379,6 +381,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)),
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { StatusError } from '@/misc/status-error.js';
import type { UtilityService } from '@/core/UtilityService.js';
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { AccountMoveService } from '@/core/AccountMoveService.js';
Expand Down Expand Up @@ -101,6 +102,8 @@ export class ApPersonService implements OnModuleInit {

@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

private roleService: RoleService,
) {
}

Expand Down Expand Up @@ -239,6 +242,11 @@ export class ApPersonService implements OnModuleInit {
return this.apImageService.resolveImage(user, img).catch(() => null);
}));

if (((avatar != null && avatar.id !== null) || (banner != null && banner.id !== null))
&& !(await this.roleService.getUserPolicies(user.id)).canUpdateBioMedia) {
return {};
}

/*
we don't want to return nulls on errors! if the database fields
are already null, nothing changes; if the database has old
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/models/json-schema/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ export const packedRolePoliciesSchema = {
type: 'boolean',
optional: false, nullable: false,
},
canUpdateBioMedia: {
type: 'boolean',
optional: false, nullable: false,
},
pinLimit: {
type: 'integer',
optional: false, nullable: false,
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/server/api/endpoints/i/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;

if (ps.avatarId) {
if (!(await roleService.getUserPolicies(user.id)).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);
Expand All @@ -337,6 +339,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}

if (ps.bannerId) {
if (!(await roleService.getUserPolicies(user.id)).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);
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const ROLE_POLICIES = [
'canHideAds',
'driveCapacityMb',
'alwaysMarkNsfw',
'canUpdateBioMedia',
'pinLimit',
'antennaLimit',
'wordMuteLimit',
Expand Down
20 changes: 20 additions & 0 deletions packages/frontend/src/pages/admin/roles.editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFolder>

<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateBioMedia, 'canUpdateBioMedia'])">
<template #label>{{ i18n.ts._role._options.canUpdateBioMedia }}</template>
<template #suffix>
<span v-if="role.policies.canUpdateBioMedia.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ role.policies.canUpdateBioMedia.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canUpdateBioMedia)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="role.policies.canUpdateBioMedia.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
<MkSwitch v-model="role.policies.canUpdateBioMedia.value" :disabled="role.policies.canUpdateBioMedia.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
<MkRange v-model="role.policies.canUpdateBioMedia.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
<template #label>{{ i18n.ts._role.priority }}</template>
</MkRange>
</div>
</MkFolder>

<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])">
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
<template #suffix>
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend/src/pages/admin/roles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch>
</MkFolder>

<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateBioMedia, 'canUpdateBioMedia'])">
<template #label>{{ i18n.ts._role._options.canUpdateBioMedia }}</template>
<template #suffix>{{ policies.canUpdateBioMedia ? i18n.ts.yes : i18n.ts.no }}</template>
<MkSwitch v-model="policies.canUpdateBioMedia">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</MkFolder>

<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])">
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
<template #suffix>{{ policies.pinLimit }}</template>
Expand Down
1 change: 1 addition & 0 deletions packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4725,6 +4725,7 @@ export type components = {
canHideAds: boolean;
driveCapacityMb: number;
alwaysMarkNsfw: boolean;
canUpdateBioMedia: boolean;
pinLimit: number;
antennaLimit: number;
wordMuteLimit: number;
Expand Down

0 comments on commit 65f0d53

Please sign in to comment.