(add) ロール制御
This commit is contained in:
parent
8608df20b2
commit
7694d17b2e
|
@ -1665,6 +1665,7 @@ export interface Locale {
|
||||||
"gtlAvailable": string;
|
"gtlAvailable": string;
|
||||||
"ltlAvailable": string;
|
"ltlAvailable": string;
|
||||||
"canPublicNote": string;
|
"canPublicNote": string;
|
||||||
|
"canScheduleNote": string;
|
||||||
"canInvite": string;
|
"canInvite": string;
|
||||||
"inviteLimit": string;
|
"inviteLimit": string;
|
||||||
"inviteLimitCycle": string;
|
"inviteLimitCycle": string;
|
||||||
|
|
|
@ -1574,6 +1574,7 @@ _role:
|
||||||
gtlAvailable: "グローバルタイムラインの閲覧"
|
gtlAvailable: "グローバルタイムラインの閲覧"
|
||||||
ltlAvailable: "ローカルタイムラインの閲覧"
|
ltlAvailable: "ローカルタイムラインの閲覧"
|
||||||
canPublicNote: "パブリック投稿の許可"
|
canPublicNote: "パブリック投稿の許可"
|
||||||
|
canScheduleNote: "予約投稿の許可"
|
||||||
canInvite: "サーバー招待コードの発行"
|
canInvite: "サーバー招待コードの発行"
|
||||||
inviteLimit: "招待コードの作成可能数"
|
inviteLimit: "招待コードの作成可能数"
|
||||||
inviteLimitCycle: "招待コードの発行間隔"
|
inviteLimitCycle: "招待コードの発行間隔"
|
||||||
|
|
|
@ -27,6 +27,7 @@ export type RolePolicies = {
|
||||||
gtlAvailable: boolean;
|
gtlAvailable: boolean;
|
||||||
ltlAvailable: boolean;
|
ltlAvailable: boolean;
|
||||||
canPublicNote: boolean;
|
canPublicNote: boolean;
|
||||||
|
canScheduleNote: boolean;
|
||||||
canInvite: boolean;
|
canInvite: boolean;
|
||||||
inviteLimit: number;
|
inviteLimit: number;
|
||||||
inviteLimitCycle: number;
|
inviteLimitCycle: number;
|
||||||
|
@ -53,6 +54,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
gtlAvailable: true,
|
gtlAvailable: true,
|
||||||
ltlAvailable: true,
|
ltlAvailable: true,
|
||||||
canPublicNote: true,
|
canPublicNote: true,
|
||||||
|
canScheduleNote: true,
|
||||||
canInvite: false,
|
canInvite: false,
|
||||||
inviteLimit: 0,
|
inviteLimit: 0,
|
||||||
inviteLimitCycle: 60 * 24 * 7,
|
inviteLimitCycle: 60 * 24 * 7,
|
||||||
|
@ -303,6 +305,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
||||||
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
||||||
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
||||||
|
canScheduleNote: calc('canScheduleNote', vs => vs.some(v => v === true)),
|
||||||
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
||||||
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
|
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
|
||||||
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
@ -132,6 +133,12 @@ export const meta = {
|
||||||
code: 'NO_SUCH_SCHEDULE',
|
code: 'NO_SUCH_SCHEDULE',
|
||||||
id: '44dee229-8da1-4a61-856d-e3a4bbc12032',
|
id: '44dee229-8da1-4a61-856d-e3a4bbc12032',
|
||||||
},
|
},
|
||||||
|
rolePermissionDenied: {
|
||||||
|
message: 'You are not assigned to a required role.',
|
||||||
|
code: 'ROLE_PERMISSION_DENIED',
|
||||||
|
kind: 'permission',
|
||||||
|
id: '7f86f06f-7e15-4057-8561-f4b6d4ac755a',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -234,6 +241,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private noteCreateService: NoteCreateService,
|
private noteCreateService: NoteCreateService,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
|
@ -375,6 +383,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ps.schedule) {
|
if (ps.schedule) {
|
||||||
|
const canCreateScheduledNote = (await this.roleService.getUserPolicies(me.id)).canScheduleNote;
|
||||||
|
if (!canCreateScheduledNote) {
|
||||||
|
throw new ApiError(meta.errors.rolePermissionDenied);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ps.schedule.expiresAt) {
|
if (!ps.schedule.expiresAt) {
|
||||||
throw new ApiError(meta.errors.specifyScheduleDate);
|
throw new ApiError(meta.errors.specifyScheduleDate);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canScheduleNote',
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -929,13 +929,13 @@ function openOtherSettingsMenu(ev: MouseEvent) {
|
||||||
text: i18n.ts.reactionAcceptance,
|
text: i18n.ts.reactionAcceptance,
|
||||||
icon: reactionAcceptanceIcon,
|
icon: reactionAcceptanceIcon,
|
||||||
action: toggleReactionAcceptance,
|
action: toggleReactionAcceptance,
|
||||||
}, {
|
}, ($i.policies?.canScheduleNote) ? {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
text: i18n.ts.schedulePost,
|
text: i18n.ts.schedulePost,
|
||||||
icon: 'ti ti-calendar-time',
|
icon: 'ti ti-calendar-time',
|
||||||
indicate: (schedule != null),
|
indicate: (schedule != null),
|
||||||
action: toggleSchedule,
|
action: toggleSchedule,
|
||||||
}, null, {
|
} : undefined, ...(($i.policies?.canScheduleNote) ? [ null, {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
text: i18n.ts._schedulePost.list,
|
text: i18n.ts._schedulePost.list,
|
||||||
icon: 'ti ti-calendar-event',
|
icon: 'ti ti-calendar-event',
|
||||||
|
@ -944,7 +944,7 @@ function openOtherSettingsMenu(ev: MouseEvent) {
|
||||||
emit('cancel');
|
emit('cancel');
|
||||||
listSchedulePost();
|
listSchedulePost();
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target, {
|
}] : [])], ev.currentTarget ?? ev.target, {
|
||||||
align: 'right',
|
align: 'right',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ export const ROLE_POLICIES = [
|
||||||
'gtlAvailable',
|
'gtlAvailable',
|
||||||
'ltlAvailable',
|
'ltlAvailable',
|
||||||
'canPublicNote',
|
'canPublicNote',
|
||||||
|
'canScheduleNote',
|
||||||
'canInvite',
|
'canInvite',
|
||||||
'inviteLimit',
|
'inviteLimit',
|
||||||
'inviteLimitCycle',
|
'inviteLimitCycle',
|
||||||
|
|
|
@ -174,6 +174,7 @@ export const navbarItemDef = reactive({
|
||||||
scheduledNotes: {
|
scheduledNotes: {
|
||||||
title: i18n.ts._schedulePost.list,
|
title: i18n.ts._schedulePost.list,
|
||||||
icon: 'ti ti-calendar-event',
|
icon: 'ti ti-calendar-event',
|
||||||
|
show: computed(() => $i && $i.policies?.canScheduleNote),
|
||||||
action: (ev) => {
|
action: (ev) => {
|
||||||
os.listSchedulePost();
|
os.listSchedulePost();
|
||||||
},
|
},
|
||||||
|
|
|
@ -160,6 +160,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canScheduleNote, 'canScheduleNote'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canScheduleNote }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canScheduleNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canScheduleNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canScheduleNote)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canScheduleNote.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canScheduleNote.value" :disabled="role.policies.canScheduleNote.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canScheduleNote.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.canInvite, 'canInvite'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
|
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
@ -549,7 +569,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkRange from '@/components/MkRange.vue';
|
import MkRange from '@/components/MkRange.vue';
|
||||||
import FormSlot from '@/components/form/slot.vue';
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ROLE_POLICIES } from '@/const';
|
import { ROLE_POLICIES } from '@/const.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { deepClone } from '@/scripts/clone.js';
|
import { deepClone } from '@/scripts/clone.js';
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canScheduleNote, 'canScheduleNote'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canScheduleNote }}</template>
|
||||||
|
<template #suffix>{{ policies.canScheduleNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canScheduleNote">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
|
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
|
||||||
<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template>
|
<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
|
Loading…
Reference in New Issue