mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
Merge pull request #6084 from logto-io/gao-org-role-types
feat: organization role types
This commit is contained in:
commit
022b5fe6cf
46 changed files with 187 additions and 71 deletions
|
@ -1,4 +1,4 @@
|
|||
import { type OrganizationRole } from '@logto/schemas';
|
||||
import { roleTypeToKey, type OrganizationRole } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
@ -81,7 +81,7 @@ function OrganizationRoleDetails() {
|
|||
<DetailsPageHeader
|
||||
icon={<ThemedIcon for={OrgRoleIcon} size={60} />}
|
||||
title={data.name}
|
||||
primaryTag={t('organization_role_details.org_role')}
|
||||
primaryTag={t(`roles.type_${roleTypeToKey[data.type]}`)}
|
||||
identifier={{ name: 'ID', value: data.id }}
|
||||
actionMenuItems={[
|
||||
{
|
||||
|
|
|
@ -1,19 +1,32 @@
|
|||
import { type OrganizationRole } from '@logto/schemas';
|
||||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import { RoleType, type OrganizationRole } from '@logto/schemas';
|
||||
import { useCallback } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import Button from '@/ds-components/Button';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import ModalLayout from '@/ds-components/ModalLayout';
|
||||
import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
|
||||
import TextInput from '@/ds-components/TextInput';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
|
||||
type FormData = Pick<OrganizationRole, 'name' | 'description'>;
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type FormData = Pick<OrganizationRole, 'name' | 'description' | 'type'>;
|
||||
|
||||
type RadioOption = { key: AdminConsoleKey; value: RoleType };
|
||||
|
||||
const radioOptions: RadioOption[] = [
|
||||
{ key: 'roles.type_user', value: RoleType.User },
|
||||
{ key: 'roles.type_machine_to_machine', value: RoleType.MachineToMachine },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
readonly isOpen: boolean;
|
||||
|
@ -25,10 +38,11 @@ function CreateOrganizationRoleModal({ isOpen, onClose }: Props) {
|
|||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
reset,
|
||||
} = useForm<FormData>();
|
||||
} = useForm<FormData>({ defaultValues: { type: RoleType.User } });
|
||||
|
||||
const onCloseHandler = useCallback(
|
||||
(createdData?: OrganizationRole) => {
|
||||
|
@ -72,9 +86,10 @@ function CreateOrganizationRoleModal({ isOpen, onClose }: Props) {
|
|||
onClick={submit}
|
||||
/>
|
||||
}
|
||||
className={styles.createModal}
|
||||
onClose={onCloseHandler}
|
||||
>
|
||||
<FormField isRequired title="organization_template.roles.create_modal.name_field">
|
||||
<FormField isRequired title="organization_template.roles.create_modal.name">
|
||||
<TextInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
|
@ -83,13 +98,36 @@ function CreateOrganizationRoleModal({ isOpen, onClose }: Props) {
|
|||
{...register('name', { required: true })}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="organization_template.permissions.description_field_name">
|
||||
<FormField title="organization_template.roles.create_modal.description">
|
||||
<TextInput
|
||||
placeholder={t('organization_role_details.general.description_field_placeholder')}
|
||||
error={Boolean(errors.description)}
|
||||
{...register('description')}
|
||||
/>
|
||||
</FormField>
|
||||
{/* TODO: Remove */}
|
||||
{isDevFeaturesEnabled && (
|
||||
<FormField title="organization_template.roles.create_modal.type">
|
||||
<Controller
|
||||
name="type"
|
||||
control={control}
|
||||
render={({ field: { onChange, value, name } }) => (
|
||||
<RadioGroup
|
||||
name={name}
|
||||
className={styles.roleTypes}
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
}}
|
||||
>
|
||||
{radioOptions.map(({ key, value }) => (
|
||||
<Radio key={value} title={<DynamicT forKey={key} />} value={value} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
</ModalLayout>
|
||||
</ReactModal>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.createModal {
|
||||
.roleTypes {
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
}
|
||||
|
||||
.permissions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type OrganizationRoleWithScopes } from '@logto/schemas';
|
||||
import { roleTypeToKey, type OrganizationRoleWithScopes } from '@logto/schemas';
|
||||
import { cond } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -88,6 +88,14 @@ function OrganizationRoles() {
|
|||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: <DynamicT forKey="organization_template.roles.type_column" />,
|
||||
dataIndex: 'type',
|
||||
colSpan: 4,
|
||||
render: ({ type }) => {
|
||||
return <DynamicT forKey={`roles.type_${roleTypeToKey[type]}`} />;
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClickHandler={({ id }) => {
|
||||
navigate(id);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { RoleType, type RoleResponse } from '@logto/schemas';
|
||||
import { RoleType, roleTypeToKey, type RoleResponse } from '@logto/schemas';
|
||||
import { Theme } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -108,11 +108,7 @@ function Roles() {
|
|||
title: t('roles.col_type'),
|
||||
dataIndex: 'type',
|
||||
colSpan: 4,
|
||||
render: ({ type }) => (
|
||||
<Breakable>
|
||||
{type === RoleType.User ? t('roles.type_user') : t('roles.type_machine_to_machine')}
|
||||
</Breakable>
|
||||
),
|
||||
render: ({ type }) => <Breakable>{t(`roles.type_${roleTypeToKey[type]}`)}</Breakable>,
|
||||
},
|
||||
{
|
||||
title: t('roles.col_description'),
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
type OrganizationRole,
|
||||
type OrganizationRoleWithScopes,
|
||||
type Scope,
|
||||
type RoleType,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import { authedAdminApi } from './api.js';
|
||||
|
@ -11,6 +12,7 @@ import { ApiFactory } from './factory.js';
|
|||
export type CreateOrganizationRolePostData = {
|
||||
name: string;
|
||||
description?: string;
|
||||
type?: RoleType;
|
||||
organizationScopeIds?: string[];
|
||||
resourceScopeIds?: string[];
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
ApplicationType,
|
||||
type ApplicationWithOrganizationRoles,
|
||||
type Application,
|
||||
RoleType,
|
||||
} from '@logto/schemas';
|
||||
import { HTTPError } from 'ky';
|
||||
|
||||
|
@ -70,8 +71,14 @@ devFeatureTest.describe('organization application APIs', () => {
|
|||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const app = applications[0]!;
|
||||
const roles = await Promise.all([
|
||||
organizationApi.roleApi.create({ name: generateTestName() }),
|
||||
organizationApi.roleApi.create({ name: generateTestName() }),
|
||||
organizationApi.roleApi.create({
|
||||
name: generateTestName(),
|
||||
type: RoleType.MachineToMachine,
|
||||
}),
|
||||
organizationApi.roleApi.create({
|
||||
name: generateTestName(),
|
||||
type: RoleType.MachineToMachine,
|
||||
}),
|
||||
]);
|
||||
const roleIds = roles.map(({ id }) => id);
|
||||
await organizationApi.addApplicationRoles(organizationId, app.id, roleIds);
|
||||
|
@ -202,7 +209,10 @@ devFeatureTest.describe('organization application APIs', () => {
|
|||
|
||||
it('should be able to add and delete organization application role', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const role = await organizationApi.roleApi.create({ name: `test-${generateTestName()}` });
|
||||
const role = await organizationApi.roleApi.create({
|
||||
name: `test-${generateTestName()}`,
|
||||
type: RoleType.MachineToMachine,
|
||||
});
|
||||
const application = await createApplication(
|
||||
generateTestName(),
|
||||
ApplicationType.MachineToMachine
|
||||
|
@ -227,5 +237,27 @@ devFeatureTest.describe('organization application APIs', () => {
|
|||
assert(response instanceof HTTPError);
|
||||
expect(response.response.status).toBe(422);
|
||||
});
|
||||
|
||||
it('should fail when try to add role that is not machine-to-machine type', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const role = await organizationApi.roleApi.create({
|
||||
name: `test-${generateTestName()}`,
|
||||
type: RoleType.User,
|
||||
});
|
||||
const application = await createApplication(
|
||||
generateTestName(),
|
||||
ApplicationType.MachineToMachine
|
||||
);
|
||||
await organizationApi.applications.add(organization.id, [application.id]);
|
||||
|
||||
const response = await organizationApi
|
||||
.addApplicationRoles(organization.id, application.id, [role.id])
|
||||
.catch((error: unknown) => error);
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.status).toBe(422);
|
||||
expect(await response.response.json()).toMatchObject(
|
||||
expect.objectContaining({ code: 'entity.db_constraint_violated' })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import assert from 'node:assert';
|
||||
|
||||
import { RoleType } from '@logto/schemas';
|
||||
import { HTTPError } from 'ky';
|
||||
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
import { UserApiTest } from '#src/helpers/user.js';
|
||||
import { generateTestName } from '#src/utils.js';
|
||||
import { devFeatureTest, generateTestName } from '#src/utils.js';
|
||||
|
||||
describe('organization user APIs', () => {
|
||||
describe('organization get users', () => {
|
||||
|
@ -287,6 +288,25 @@ describe('organization user APIs', () => {
|
|||
expect.objectContaining({ code: 'entity.not_found' })
|
||||
);
|
||||
});
|
||||
|
||||
devFeatureTest.it('should fail when try to add role that is not user type', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const user = await userApi.create({ username: generateTestName() });
|
||||
const role = await roleApi.create({
|
||||
name: generateTestName(),
|
||||
type: RoleType.MachineToMachine,
|
||||
});
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user.id]);
|
||||
const response = await organizationApi
|
||||
.addUserRoles(organization.id, user.id, [role.id])
|
||||
.catch((error: unknown) => error);
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.status).toBe(422);
|
||||
expect(await response.response.json()).toMatchObject(
|
||||
expect.objectContaining({ code: 'entity.db_constraint_violated' })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization - user - organization role - organization scopes relation', () => {
|
||||
|
|
|
@ -46,7 +46,7 @@ export const createM2mRoleAndAssignPermissions = async (
|
|||
await expectModalWithTitle(page, 'Create role');
|
||||
|
||||
await expect(page).toClick('div[class*=radioGroup][class$=roleTypes] div[class$=content]', {
|
||||
text: 'Machine-to-machine role',
|
||||
text: 'Machine-to-machine',
|
||||
});
|
||||
|
||||
await expect(page).toFillForm('.ReactModalPortal form', {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Organisationsrollendetails',
|
||||
back_to_org_roles: 'Zurück zu den Organisationsrollen',
|
||||
org_role: 'Org-Rolle',
|
||||
delete_confirm:
|
||||
'Dadurch werden die mit dieser Rolle verbundenen Berechtigungen von den betroffenen Benutzern entfernt und die Beziehungen zwischen Organisationsrollen, Mitgliedern in der Organisation und Organisationsberechtigungen gelöscht.',
|
||||
deleted: 'Die Organisationsrolle {{name}} wurde erfolgreich gelöscht.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Rolle erstellen',
|
||||
role_name: 'Rollenname',
|
||||
role_type: 'Rollenart',
|
||||
type_user: 'Benutzerrolle',
|
||||
type_machine_to_machine: 'Rolle von Maschine zu Maschine',
|
||||
role_description: 'Beschreibung',
|
||||
role_name_placeholder: 'Geben Sie Ihren Rollennamen ein',
|
||||
role_description_placeholder: 'Geben Sie Ihre Rollenbeschreibung ein',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Organization role details',
|
||||
back_to_org_roles: 'Back to organization roles',
|
||||
org_role: 'Organization role',
|
||||
delete_confirm:
|
||||
'Doing so will remove the permissions associated with this role from the affected users and delete the relations among organization roles, members in the organization, and organization permissions.',
|
||||
deleted: 'Organization role {{name}} was successfully deleted.',
|
||||
|
|
|
@ -8,14 +8,16 @@ const organization_template = {
|
|||
create_title: 'Create organization role',
|
||||
role_column: 'Organization role',
|
||||
permissions_column: 'Permissions',
|
||||
type_column: 'Role type',
|
||||
placeholder_title: 'Organization role',
|
||||
placeholder_description:
|
||||
'Organization role is a grouping of permissions that can be assigned to users. The permissions must come from the predefined organization permissions.',
|
||||
create_modal: {
|
||||
title: 'Create organization role',
|
||||
create: 'Create role',
|
||||
name_field: 'Role name',
|
||||
description_field: 'Description',
|
||||
name: 'Role name',
|
||||
description: 'Description',
|
||||
type: 'Role type',
|
||||
created: 'Organization role {{name}} has been successfully created.',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -6,8 +6,8 @@ const roles = {
|
|||
create: 'Create role',
|
||||
role_name: 'Role name',
|
||||
role_type: 'Role type',
|
||||
type_user: 'User role',
|
||||
type_machine_to_machine: 'Machine-to-machine role',
|
||||
type_user: 'User',
|
||||
type_machine_to_machine: 'Machine-to-machine',
|
||||
role_description: 'Description',
|
||||
role_name_placeholder: 'Enter your role name',
|
||||
role_description_placeholder: 'Enter your role description',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Detalles del rol de la organización',
|
||||
back_to_org_roles: 'Volver a los roles de la organización',
|
||||
org_role: 'Rol de la organización',
|
||||
delete_confirm:
|
||||
'Al hacerlo, se eliminarán los permisos asociados con este rol de los usuarios afectados y se borrarán las relaciones entre roles de organización, miembros en la organización y permisos de organización.',
|
||||
deleted: 'El rol de organización {{name}} se eliminó con éxito.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Crear Rol',
|
||||
role_name: 'Nombre de rol',
|
||||
role_type: 'Tipo de rol',
|
||||
type_user: 'Rol de usuario',
|
||||
type_machine_to_machine: 'Rol de máquina a máquina',
|
||||
role_description: 'Descripción',
|
||||
role_name_placeholder: 'Ingrese el nombre de su rol',
|
||||
role_description_placeholder: 'Ingrese la descripción de su rol',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: "Détails du rôle de l'organisation",
|
||||
back_to_org_roles: "Retour aux rôles de l'organisation",
|
||||
org_role: "Rôle de l'organisation",
|
||||
delete_confirm:
|
||||
"Cela supprimera les autorisations associées à ce rôle des utilisateurs concernés et supprimera les relations entre les rôles de l'organisation, les membres de l'organisation et les autorisations de l'organisation.",
|
||||
deleted: "Le rôle de l'organisation {{name}} a été supprimé avec succès.",
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Créer un rôle',
|
||||
role_name: 'Nom du rôle',
|
||||
role_type: 'Type de rôle',
|
||||
type_user: 'Rôle utilisateur',
|
||||
type_machine_to_machine: 'Rôle de machine à machine',
|
||||
role_description: 'Description',
|
||||
role_name_placeholder: 'Entrez le nom de votre rôle',
|
||||
role_description_placeholder: 'Entrez la description de votre rôle',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: "Dettagli del ruolo dell'organizzazione",
|
||||
back_to_org_roles: "Torna ai ruoli dell'organizzazione",
|
||||
org_role: "Ruolo dell'organizzazione",
|
||||
delete_confirm:
|
||||
"Facendo ciò, verranno rimossi i permessi associati a questo ruolo dagli utenti interessati e verranno eliminati i rapporti tra ruoli organizzativi, membri nell'organizzazione e permessi dell'organizzazione.",
|
||||
deleted: "Il ruolo dell'organizzazione {{name}} è stato cancellato con successo.",
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Crea Ruolo',
|
||||
role_name: 'Nome ruolo',
|
||||
role_type: 'Tipo ruolo',
|
||||
type_user: 'Ruolo utente',
|
||||
type_machine_to_machine: 'Ruolo da macchina a macchina',
|
||||
role_description: 'Descrizione',
|
||||
role_name_placeholder: 'Inserisci il nome del tuo ruolo',
|
||||
role_description_placeholder: 'Inserisci la descrizione del tuo ruolo',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: '組織の役割の詳細',
|
||||
back_to_org_roles: '組織の役割に戻る',
|
||||
org_role: '組織の役割',
|
||||
delete_confirm:
|
||||
'これにより、関連するユーザーからこのロールに関連付けられた権限が削除され、組織の役割、組織のメンバー、および組織の権限間の関係が削除されます。',
|
||||
deleted: '組織の役割{{name}}が正常に削除されました。',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'ロールを作成する',
|
||||
role_name: '役割名',
|
||||
role_type: '役割タイプ',
|
||||
type_user: 'ユーザーの役割',
|
||||
type_machine_to_machine: 'マシン間の役割',
|
||||
role_description: '説明',
|
||||
role_name_placeholder: 'ロールの名前を入力してください',
|
||||
role_description_placeholder: 'ロールの説明を入力してください',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: '조직 역할 세부 정보',
|
||||
back_to_org_roles: '조직 역할로 돌아가기',
|
||||
org_role: '조직 역할',
|
||||
delete_confirm:
|
||||
'이렇게 하면 해당 역할과 관련된 사용자의 권한이 제거되고 조직 역할, 조직 구성원 및 조직 권한 간의 관계가 삭제됩니다.',
|
||||
deleted: '조직 역할 {{name}} 이(가) 성공적으로 삭제되었습니다.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: '역할 생성',
|
||||
role_name: '역할 이름',
|
||||
role_type: '역할 유형',
|
||||
type_user: '사용자 역할',
|
||||
type_machine_to_machine: '기계 간 역할',
|
||||
role_description: '설명',
|
||||
role_name_placeholder: '역할 이름을 입력하세요',
|
||||
role_description_placeholder: '역할 설명을 입력하세요',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Szczegóły roli organizacji',
|
||||
back_to_org_roles: 'Powrót do ról organizacyjnych',
|
||||
org_role: 'Rola organizacji',
|
||||
delete_confirm:
|
||||
'W wyniku tego zostaną usunięte uprawnienia związane z tą rolą od dotkniętych użytkowników i zostaną usunięte związki między rolami organizacyjnymi, członkami organizacji a uprawnieniami organizacji.',
|
||||
deleted: 'Rola organizacji {{name}} została pomyślnie usunięta.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Utwórz rolę',
|
||||
role_name: 'Nazwa roli',
|
||||
role_type: 'Typ roli',
|
||||
type_user: 'Rola użytkownika',
|
||||
type_machine_to_machine: 'Rola maszyny do maszyny',
|
||||
role_description: 'Opis',
|
||||
role_name_placeholder: 'Wprowadź nazwę swojej roli',
|
||||
role_description_placeholder: 'Wprowadź opis swojej roli',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Detalhes da função da organização',
|
||||
back_to_org_roles: 'Voltar para os papéis da organização',
|
||||
org_role: 'Função da organização',
|
||||
delete_confirm:
|
||||
'Ao fazer isso, os privilégios associados a esta função serão removidos dos usuários afetados e as relações entre funções da organização, membros na organização e permissões da organização serão excluídas.',
|
||||
deleted: 'O papel da organização {{name}} foi excluído com sucesso.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Criar função',
|
||||
role_name: 'Nome da função',
|
||||
role_type: 'Tipo de função',
|
||||
type_user: 'Função do usuário',
|
||||
type_machine_to_machine: 'Função de máquina para máquina',
|
||||
role_description: 'Descrição',
|
||||
role_name_placeholder: 'Insira o nome da sua função',
|
||||
role_description_placeholder: 'Insira a descrição da sua função',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Detalhes da função da organização',
|
||||
back_to_org_roles: 'Voltar aos papéis da organização',
|
||||
org_role: 'Função da organização',
|
||||
delete_confirm:
|
||||
'Ao fazê-lo, serão removidas as permissões associadas a esta função dos utilizadores afetados e serão eliminadas as relações entre funções da organização, membros na organização e permissões da organização.',
|
||||
deleted: 'O papel da organização {{name}} foi eliminado com sucesso.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Criar papel',
|
||||
role_name: 'Nome do papel',
|
||||
role_type: 'Tipo de papel',
|
||||
type_user: 'Função de usuário',
|
||||
type_machine_to_machine: 'Função de máquina para máquina',
|
||||
role_description: 'Descrição',
|
||||
role_name_placeholder: 'Digite o nome do papel',
|
||||
role_description_placeholder: 'Digite a descrição do papel',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Детали роли организации',
|
||||
back_to_org_roles: 'Вернуться к ролям в организации',
|
||||
org_role: 'Роль организации',
|
||||
delete_confirm:
|
||||
'При этом будут удалены разрешения, связанные с этой ролью, у затронутых пользователей, и будут удалены связи между ролями организации, членами организации и правами организации.',
|
||||
deleted: 'Роль в организации {{name}} была успешно удалена.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Создать роль',
|
||||
role_name: 'Имя роли',
|
||||
role_type: 'Тип роли',
|
||||
type_user: 'Роль пользователя',
|
||||
type_machine_to_machine: 'Роль машина-машина',
|
||||
role_description: 'Описание',
|
||||
role_name_placeholder: 'Введите название роли',
|
||||
role_description_placeholder: 'Введите описание роли',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: 'Kuruluş rolü ayrıntıları',
|
||||
back_to_org_roles: 'Organizasyon rollerine geri dön',
|
||||
org_role: 'Kuruluş rolü',
|
||||
delete_confirm:
|
||||
'Bunu yapmak, etkilenen kullanıcılardan bu role ilişkilendirilen izinleri kaldıracak ve organizasyon rolleri, organizasyon üyeleri ve organizasyon izinleri arasındaki ilişkileri silecektir.',
|
||||
deleted: 'Kuruluş rolü {{name}} başarıyla silindi.',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: 'Rol Oluştur',
|
||||
role_name: 'Rol adı',
|
||||
role_type: 'Rol tipi',
|
||||
type_user: 'Kullanıcı rolü',
|
||||
type_machine_to_machine: 'Makineden makineye rol',
|
||||
role_description: 'Açıklama',
|
||||
role_name_placeholder: 'Rol adınızı girin',
|
||||
role_description_placeholder: 'Rol açıklamanızı girin',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: '组织角色详情',
|
||||
back_to_org_roles: '返回组织角色',
|
||||
org_role: '组织角色',
|
||||
delete_confirm:
|
||||
'这样做将从受影响的用户中删除与此角色关联的权限,并删除组织角色、组织成员和组织权限之间的关系。',
|
||||
deleted: '组织角色 {{name}} 已成功删除。',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: '创建角色',
|
||||
role_name: '角色名称',
|
||||
role_type: '角色类型',
|
||||
type_user: '用户角色',
|
||||
type_machine_to_machine: '机器对机器角色',
|
||||
role_description: '描述',
|
||||
role_name_placeholder: '输入你的角色名称',
|
||||
role_description_placeholder: '输入你的角色描述',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: '組織角色詳情',
|
||||
back_to_org_roles: '返回組織角色',
|
||||
org_role: '組織角色',
|
||||
delete_confirm:
|
||||
'這樣做將會從受影響的使用者中移除與此角色相關聯的權限,並刪除組織角色、組織成員和組織權限之間的關係。',
|
||||
deleted: '組織角色 {{name}} 已成功刪除。',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: '創建角色',
|
||||
role_name: '角色名稱',
|
||||
role_type: '角色類型',
|
||||
type_user: '用戶角色',
|
||||
type_machine_to_machine: '機器對機器角色',
|
||||
role_description: '描述',
|
||||
role_name_placeholder: '輸入你的角色名稱',
|
||||
role_description_placeholder: '輸入你的角色描述',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
const organization_role_details = {
|
||||
page_title: '組織角色詳情',
|
||||
back_to_org_roles: '返回組織角色',
|
||||
org_role: '組織角色',
|
||||
delete_confirm:
|
||||
'這樣做將會從受影響的使用者中移除與此角色相關聯的權限,並刪除組織角色、組織成員和組織權限之間的關係。',
|
||||
deleted: '組織角色 {{name}} 已成功刪除。',
|
||||
|
|
|
@ -6,8 +6,6 @@ const roles = {
|
|||
create: '建立角色',
|
||||
role_name: '角色名稱',
|
||||
role_type: '角色類型',
|
||||
type_user: '使用者角色',
|
||||
type_machine_to_machine: '機器對機器角色',
|
||||
role_description: '描述',
|
||||
role_name_placeholder: '輸入你的角色名稱',
|
||||
role_description_placeholder: '輸入你的角色描述',
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { sql } from '@silverhand/slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table organization_roles
|
||||
add column type role_type not null default 'User';
|
||||
create function check_organization_role_type(role_id varchar(21), target_type role_type) returns boolean as
|
||||
$$ begin
|
||||
return (select type from organization_roles where id = role_id) = target_type;
|
||||
end; $$ language plpgsql;
|
||||
alter table organization_role_user_relations
|
||||
add constraint organization_role_user_relations__role_type
|
||||
check (check_organization_role_type(organization_role_id, 'User'));
|
||||
alter table organization_role_application_relations
|
||||
add constraint organization_role_application_relations__role_type
|
||||
check (check_organization_role_type(organization_role_id, 'MachineToMachine'));
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table organization_role_application_relations
|
||||
drop constraint organization_role_application_relations__role_type;
|
||||
alter table organization_role_user_relations
|
||||
drop constraint organization_role_user_relations__role_type;
|
||||
alter table organization_roles
|
||||
drop column type;
|
||||
drop function check_organization_role_type;
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -1,4 +1,4 @@
|
|||
import type { Role } from '../db-entries/index.js';
|
||||
import { RoleType, type Role } from '../db-entries/index.js';
|
||||
|
||||
import { type FeaturedApplication } from './application.js';
|
||||
import { type FeaturedUser } from './user.js';
|
||||
|
@ -9,3 +9,9 @@ export type RoleResponse = Role & {
|
|||
applicationsCount: number;
|
||||
featuredApplications: FeaturedApplication[];
|
||||
};
|
||||
|
||||
/** The role type to i18n key mapping. */
|
||||
export const roleTypeToKey = Object.freeze({
|
||||
[RoleType.User]: 'user',
|
||||
[RoleType.MachineToMachine]: 'machine_to_machine',
|
||||
} as const satisfies Record<RoleType, string>);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
RoleType,
|
||||
type CreateOrganization,
|
||||
type OrganizationRole,
|
||||
type OrganizationScope,
|
||||
|
@ -147,6 +148,7 @@ const tenantRoleDescriptions: Readonly<Record<TenantRole, string>> = Object.free
|
|||
* id: 'collaborator',
|
||||
* name: 'collaborator',
|
||||
* description: 'Collaborator of the tenant, who has permissions to operate the tenant data, but not the tenant settings.',
|
||||
* type: RoleType.User,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
|
@ -158,6 +160,7 @@ export const getTenantRole = (role: TenantRole): Readonly<OrganizationRole> =>
|
|||
id: role,
|
||||
name: role,
|
||||
description: tenantRoleDescriptions[role],
|
||||
type: RoleType.User,
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,5 +12,7 @@ create table organization_role_application_relations (
|
|||
/** Application's roles in an organization should be synchronized with the application's membership in the organization. */
|
||||
foreign key (tenant_id, organization_id, application_id)
|
||||
references organization_application_relations (tenant_id, organization_id, application_id)
|
||||
on update cascade on delete cascade
|
||||
on update cascade on delete cascade,
|
||||
constraint organization_role_application_relations__role_type
|
||||
check (check_organization_role_type(organization_role_id, 'MachineToMachine'))
|
||||
);
|
||||
|
|
|
@ -12,5 +12,7 @@ create table organization_role_user_relations (
|
|||
/** User's roles in an organization should be synchronized with the user's membership in the organization. */
|
||||
foreign key (tenant_id, organization_id, user_id)
|
||||
references organization_user_relations (tenant_id, organization_id, user_id)
|
||||
on update cascade on delete cascade
|
||||
on update cascade on delete cascade,
|
||||
constraint organization_role_user_relations__role_type
|
||||
check (check_organization_role_type(organization_role_id, 'User'))
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* init_order = 1 */
|
||||
/* init_order = 1.1 */
|
||||
|
||||
/** The roles defined by the organization template. */
|
||||
create table organization_roles (
|
||||
|
@ -10,6 +10,8 @@ create table organization_roles (
|
|||
name varchar(128) not null,
|
||||
/** A brief description of the organization role. */
|
||||
description varchar(256),
|
||||
/** The type of the organization role. Same as the `type` field in the `roles` table. */
|
||||
type role_type not null default 'User',
|
||||
primary key (id),
|
||||
constraint organization_roles__name
|
||||
unique (tenant_id, name)
|
||||
|
@ -17,3 +19,8 @@ create table organization_roles (
|
|||
|
||||
create index organization_roles__id
|
||||
on organization_roles (tenant_id, id);
|
||||
|
||||
create function check_organization_role_type(role_id varchar(21), target_type role_type) returns boolean as
|
||||
$$ begin
|
||||
return (select type from organization_roles where id = role_id) = target_type;
|
||||
end; $$ language plpgsql;
|
||||
|
|
Loading…
Add table
Reference in a new issue