1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-24 12:44:04 +02:00

enhance: Add canCreateChannel role policy (#17121)

* Initial plan

* Add canCreateChannel role policy to control channel creation

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Add canCreateChannel to getUserPolicies return value

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Add canCreateChannel translations for en-US and ja-JP

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Add canCreateChannel to misskey-js rolePolicies array

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Add frontend UI for canCreateChannel policy configuration

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* fix: build autogen files

* 🎨

* migrate

* fix: unnecessary changes to non-Japanese locales

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
This commit is contained in:
Copilot
2026-05-03 17:10:17 +09:00
committed by GitHub
parent 712b51c142
commit 37412f0e1b
10 changed files with 29 additions and 2 deletions

View File

@@ -2122,6 +2122,7 @@ _role:
canSearchNotes: "ノート検索の利用" canSearchNotes: "ノート検索の利用"
canSearchUsers: "ユーザー検索の利用" canSearchUsers: "ユーザー検索の利用"
canUseTranslator: "翻訳機能の利用" canUseTranslator: "翻訳機能の利用"
canCreateChannel: "チャンネルの作成"
avatarDecorationLimit: "アイコンデコレーションの最大取付個数" avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
canImportAntennas: "アンテナのインポートを許可" canImportAntennas: "アンテナのインポートを許可"
canImportBlocking: "ブロックのインポートを許可" canImportBlocking: "ブロックのインポートを許可"

View File

@@ -47,6 +47,7 @@ export type RolePolicies = {
canSearchUsers: boolean; canSearchUsers: boolean;
canUseTranslator: boolean; canUseTranslator: boolean;
canHideAds: boolean; canHideAds: boolean;
canCreateChannel: boolean;
driveCapacityMb: number; driveCapacityMb: number;
maxFileSizeMb: number; maxFileSizeMb: number;
alwaysMarkNsfw: boolean; alwaysMarkNsfw: boolean;
@@ -88,6 +89,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canSearchUsers: true, canSearchUsers: true,
canUseTranslator: true, canUseTranslator: true,
canHideAds: false, canHideAds: false,
canCreateChannel: true,
driveCapacityMb: 100, driveCapacityMb: 100,
maxFileSizeMb: 30, maxFileSizeMb: 30,
alwaysMarkNsfw: false, alwaysMarkNsfw: false,
@@ -410,6 +412,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canSearchUsers: calc('canSearchUsers', vs => vs.some(v => v === true)), canSearchUsers: calc('canSearchUsers', vs => vs.some(v => v === true)),
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)), canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
canCreateChannel: calc('canCreateChannel', vs => vs.some(v => v === true)),
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
maxFileSizeMb: calc('maxFileSizeMb', vs => Math.max(...vs)), maxFileSizeMb: calc('maxFileSizeMb', vs => Math.max(...vs)),
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),

View File

@@ -224,6 +224,10 @@ export const packedRolePoliciesSchema = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
canCreateChannel: {
type: 'boolean',
optional: false, nullable: false,
},
driveCapacityMb: { driveCapacityMb: {
type: 'integer', type: 'integer',
optional: false, nullable: false, optional: false, nullable: false,

View File

@@ -22,6 +22,8 @@ export const meta = {
kind: 'write:channels', kind: 'write:channels',
requiredRolePolicy: 'canCreateChannel',
limit: { limit: {
duration: ms('1hour'), duration: ms('1hour'),
max: 10, max: 10,

View File

@@ -161,6 +161,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
</XFolder> </XFolder>
<XFolder v-if="matchQuery([i18n.ts._role._options.canCreateChannel, 'canCreateChannel'])" v-model:policyMeta="policyMetaModel.canCreateChannel" :isBaseRole="isBaseRole" :readonly="readonly">
<template #label>{{ i18n.ts._role._options.canCreateChannel }}</template>
<template #suffix>{{ valuesModel.canCreateChannel ? i18n.ts.yes : i18n.ts.no }}</template>
<template #default="{ disabled }">
<MkSwitch v-model="valuesModel.canCreateChannel" :disabled="disabled">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</template>
</XFolder>
<XFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])" v-model:policyMeta="policyMetaModel.driveCapacityMb" :isBaseRole="isBaseRole" :readonly="readonly"> <XFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])" v-model:policyMeta="policyMetaModel.driveCapacityMb" :isBaseRole="isBaseRole" :readonly="readonly">
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template> <template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
<template #valueText>{{ valuesModel.driveCapacityMb }}MB</template> <template #valueText>{{ valuesModel.driveCapacityMb }}MB</template>

View File

@@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPagination> </MkPagination>
</div> </div>
<div v-else-if="tab === 'owned'" class="_gaps"> <div v-else-if="tab === 'owned'" class="_gaps">
<MkButton type="routerLink" primary rounded to="/channels/new"><i class="ti ti-plus"></i> {{ i18n.ts.createNew }}</MkButton> <MkButton v-if="$i?.policies.canCreateChannel" type="routerLink" primary rounded to="/channels/new"><i class="ti ti-plus"></i> {{ i18n.ts.createNew }}</MkButton>
<MkPagination v-slot="{items}" :paginator="ownedPaginator"> <MkPagination v-slot="{items}" :paginator="ownedPaginator">
<div :class="$style.root"> <div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
@@ -74,6 +74,7 @@ import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js'; import { useRouter } from '@/router.js';
import { Paginator } from '@/utility/paginator.js'; import { Paginator } from '@/utility/paginator.js';
import { $i } from '@/i.js';
const router = useRouter(); const router = useRouter();

View File

@@ -8177,6 +8177,10 @@ export interface Locale extends ILocale {
* 翻訳機能の利用 * 翻訳機能の利用
*/ */
"canUseTranslator": string; "canUseTranslator": string;
/**
* チャンネルの作成
*/
"canCreateChannel": string;
/** /**
* アイコンデコレーションの最大取付個数 * アイコンデコレーションの最大取付個数
*/ */

View File

@@ -3466,7 +3466,7 @@ type RoleLite = components['schemas']['RoleLite'];
type RolePolicies = components['schemas']['RolePolicies']; type RolePolicies = components['schemas']['RolePolicies'];
// @public (undocumented) // @public (undocumented)
export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "scheduledNoteLimit", "watermarkAvailable"]; export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "canCreateChannel", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "scheduledNoteLimit", "watermarkAvailable"];
// @public (undocumented) // @public (undocumented)
type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json']; type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];

View File

@@ -5322,6 +5322,7 @@ export type components = {
canSearchUsers: boolean; canSearchUsers: boolean;
canUseTranslator: boolean; canUseTranslator: boolean;
canHideAds: boolean; canHideAds: boolean;
canCreateChannel: boolean;
driveCapacityMb: number; driveCapacityMb: number;
maxFileSizeMb: number; maxFileSizeMb: number;
uploadableFileTypes: string[]; uploadableFileTypes: string[];

View File

@@ -205,6 +205,7 @@ export const rolePolicies = [
'canSearchUsers', 'canSearchUsers',
'canUseTranslator', 'canUseTranslator',
'canHideAds', 'canHideAds',
'canCreateChannel',
'driveCapacityMb', 'driveCapacityMb',
'maxFileSizeMb', 'maxFileSizeMb',
'alwaysMarkNsfw', 'alwaysMarkNsfw',