diff --git a/packages/console/src/pages/OrganizationRoleDetails/index.tsx b/packages/console/src/pages/OrganizationRoleDetails/index.tsx index 835119814..4d6f404f2 100644 --- a/packages/console/src/pages/OrganizationRoleDetails/index.tsx +++ b/packages/console/src/pages/OrganizationRoleDetails/index.tsx @@ -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() { } title={data.name} - primaryTag={t('organization_role_details.org_role')} + primaryTag={t(`roles.type_${roleTypeToKey[data.type]}`)} identifier={{ name: 'ID', value: data.id }} actionMenuItems={[ { diff --git a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/CreateOrganizationRoleModal.tsx b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/CreateOrganizationRoleModal.tsx index 1338228e7..fba7aec2e 100644 --- a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/CreateOrganizationRoleModal.tsx +++ b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/CreateOrganizationRoleModal.tsx @@ -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; +import * as styles from './index.module.scss'; + +type FormData = Pick; + +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(); + } = useForm({ 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} > - + - + + {/* TODO: Remove */} + {isDevFeaturesEnabled && ( + + ( + { + onChange(value); + }} + > + {radioOptions.map(({ key, value }) => ( + } value={value} /> + ))} + + )} + /> + + )} ); diff --git a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.module.scss b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.module.scss index da411ee4a..dbd538746 100644 --- a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.module.scss +++ b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.module.scss @@ -1,5 +1,11 @@ @use '@/scss/underscore' as _; +.createModal { + .roleTypes { + margin-top: _.unit(2); + } +} + .permissions { display: flex; flex-wrap: wrap; diff --git a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx index 5c39edf8c..b0a5d3fdc 100644 --- a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx +++ b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx @@ -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: , + dataIndex: 'type', + colSpan: 4, + render: ({ type }) => { + return ; + }, + }, ]} rowClickHandler={({ id }) => { navigate(id); diff --git a/packages/console/src/pages/Roles/index.tsx b/packages/console/src/pages/Roles/index.tsx index d7b693430..0a7f7daea 100644 --- a/packages/console/src/pages/Roles/index.tsx +++ b/packages/console/src/pages/Roles/index.tsx @@ -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 }) => ( - - {type === RoleType.User ? t('roles.type_user') : t('roles.type_machine_to_machine')} - - ), + render: ({ type }) => {t(`roles.type_${roleTypeToKey[type]}`)}, }, { title: t('roles.col_description'), diff --git a/packages/integration-tests/src/api/organization-role.ts b/packages/integration-tests/src/api/organization-role.ts index eabe04ae4..57827f67a 100644 --- a/packages/integration-tests/src/api/organization-role.ts +++ b/packages/integration-tests/src/api/organization-role.ts @@ -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[]; }; diff --git a/packages/integration-tests/src/tests/api/organization/organization-application.test.ts b/packages/integration-tests/src/tests/api/organization/organization-application.test.ts index ae1f949a4..9d3514059 100644 --- a/packages/integration-tests/src/tests/api/organization/organization-application.test.ts +++ b/packages/integration-tests/src/tests/api/organization/organization-application.test.ts @@ -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' }) + ); + }); }); }); diff --git a/packages/integration-tests/src/tests/api/organization/organization-user.test.ts b/packages/integration-tests/src/tests/api/organization/organization-user.test.ts index a7d882db3..91801d43a 100644 --- a/packages/integration-tests/src/tests/api/organization/organization-user.test.ts +++ b/packages/integration-tests/src/tests/api/organization/organization-user.test.ts @@ -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', () => { diff --git a/packages/integration-tests/src/tests/console/rbac/helper.ts b/packages/integration-tests/src/tests/console/rbac/helper.ts index 653833f43..92b35b389 100644 --- a/packages/integration-tests/src/tests/console/rbac/helper.ts +++ b/packages/integration-tests/src/tests/console/rbac/helper.ts @@ -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', { diff --git a/packages/phrases/src/locales/de/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/de/translation/admin-console/organization-role-details.ts index 3c0d2f296..04152b11f 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/organization-role-details.ts @@ -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.', diff --git a/packages/phrases/src/locales/de/translation/admin-console/roles.ts b/packages/phrases/src/locales/de/translation/admin-console/roles.ts index acd19b414..a214a862a 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/en/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/en/translation/admin-console/organization-role-details.ts index bace0551e..2d9b45fac 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/organization-role-details.ts @@ -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.', diff --git a/packages/phrases/src/locales/en/translation/admin-console/organization-template.ts b/packages/phrases/src/locales/en/translation/admin-console/organization-template.ts index 75b47061a..62d555799 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/organization-template.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/organization-template.ts @@ -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.', }, }, diff --git a/packages/phrases/src/locales/en/translation/admin-console/roles.ts b/packages/phrases/src/locales/en/translation/admin-console/roles.ts index 6c40142aa..2f120b082 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/es/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/es/translation/admin-console/organization-role-details.ts index 8da044728..705833aaf 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/organization-role-details.ts @@ -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.', diff --git a/packages/phrases/src/locales/es/translation/admin-console/roles.ts b/packages/phrases/src/locales/es/translation/admin-console/roles.ts index fea4bff00..30b2030be 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/fr/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/fr/translation/admin-console/organization-role-details.ts index 36c932c9f..bcac051fa 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/organization-role-details.ts @@ -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.", diff --git a/packages/phrases/src/locales/fr/translation/admin-console/roles.ts b/packages/phrases/src/locales/fr/translation/admin-console/roles.ts index 81beb9aea..00db35af7 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/it/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/it/translation/admin-console/organization-role-details.ts index 5a20b180c..29051a37e 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/organization-role-details.ts @@ -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.", diff --git a/packages/phrases/src/locales/it/translation/admin-console/roles.ts b/packages/phrases/src/locales/it/translation/admin-console/roles.ts index 6541e4eab..91c2f0dcc 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/ja/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/ja/translation/admin-console/organization-role-details.ts index 4a2c15f1a..7e290d2f2 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/organization-role-details.ts @@ -1,7 +1,6 @@ const organization_role_details = { page_title: '組織の役割の詳細', back_to_org_roles: '組織の役割に戻る', - org_role: '組織の役割', delete_confirm: 'これにより、関連するユーザーからこのロールに関連付けられた権限が削除され、組織の役割、組織のメンバー、および組織の権限間の関係が削除されます。', deleted: '組織の役割{{name}}が正常に削除されました。', diff --git a/packages/phrases/src/locales/ja/translation/admin-console/roles.ts b/packages/phrases/src/locales/ja/translation/admin-console/roles.ts index 740aa9db4..2ad2910d5 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/roles.ts @@ -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: 'ロールの説明を入力してください', diff --git a/packages/phrases/src/locales/ko/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/ko/translation/admin-console/organization-role-details.ts index c7e93a2b7..370bc7435 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/organization-role-details.ts @@ -1,7 +1,6 @@ const organization_role_details = { page_title: '조직 역할 세부 정보', back_to_org_roles: '조직 역할로 돌아가기', - org_role: '조직 역할', delete_confirm: '이렇게 하면 해당 역할과 관련된 사용자의 권한이 제거되고 조직 역할, 조직 구성원 및 조직 권한 간의 관계가 삭제됩니다.', deleted: '조직 역할 {{name}} 이(가) 성공적으로 삭제되었습니다.', diff --git a/packages/phrases/src/locales/ko/translation/admin-console/roles.ts b/packages/phrases/src/locales/ko/translation/admin-console/roles.ts index 1f0da4b2e..fdbd0e51c 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/roles.ts @@ -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: '역할 설명을 입력하세요', diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/organization-role-details.ts index d9b71f053..5317294cc 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/organization-role-details.ts @@ -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.', diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts index 96dfb0d30..989a0726a 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/organization-role-details.ts index 9b7a502d4..769f5b231 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/organization-role-details.ts @@ -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.', diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts index f79cb268b..53e3ed02f 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/organization-role-details.ts index 488bc5c0e..a4c0b5e7a 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/organization-role-details.ts @@ -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.', diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts index e92c0256e..269c956a4 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/ru/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/ru/translation/admin-console/organization-role-details.ts index b22af529d..3594debd2 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/organization-role-details.ts @@ -1,7 +1,6 @@ const organization_role_details = { page_title: 'Детали роли организации', back_to_org_roles: 'Вернуться к ролям в организации', - org_role: 'Роль организации', delete_confirm: 'При этом будут удалены разрешения, связанные с этой ролью, у затронутых пользователей, и будут удалены связи между ролями организации, членами организации и правами организации.', deleted: 'Роль в организации {{name}} была успешно удалена.', diff --git a/packages/phrases/src/locales/ru/translation/admin-console/roles.ts b/packages/phrases/src/locales/ru/translation/admin-console/roles.ts index 012ea6627..257d19583 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/roles.ts @@ -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: 'Введите описание роли', diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/organization-role-details.ts index 12c26925f..e6d42de8d 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/organization-role-details.ts @@ -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.', diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts index 3b4cafd33..3f713908b 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/organization-role-details.ts index d657f860d..38a8fa767 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/organization-role-details.ts @@ -1,7 +1,6 @@ const organization_role_details = { page_title: '组织角色详情', back_to_org_roles: '返回组织角色', - org_role: '组织角色', delete_confirm: '这样做将从受影响的用户中删除与此角色关联的权限,并删除组织角色、组织成员和组织权限之间的关系。', deleted: '组织角色 {{name}} 已成功删除。', diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts index c71ae4ee3..cb744cfb4 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts @@ -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: '输入你的角色描述', diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/organization-role-details.ts index b11cc005a..9bdf9b5df 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/organization-role-details.ts @@ -1,7 +1,6 @@ const organization_role_details = { page_title: '組織角色詳情', back_to_org_roles: '返回組織角色', - org_role: '組織角色', delete_confirm: '這樣做將會從受影響的使用者中移除與此角色相關聯的權限,並刪除組織角色、組織成員和組織權限之間的關係。', deleted: '組織角色 {{name}} 已成功刪除。', diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts index 0c61476df..b4661638d 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts @@ -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: '輸入你的角色描述', diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/organization-role-details.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/organization-role-details.ts index bdce17089..c9d187a47 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/organization-role-details.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/organization-role-details.ts @@ -1,7 +1,6 @@ const organization_role_details = { page_title: '組織角色詳情', back_to_org_roles: '返回組織角色', - org_role: '組織角色', delete_confirm: '這樣做將會從受影響的使用者中移除與此角色相關聯的權限,並刪除組織角色、組織成員和組織權限之間的關係。', deleted: '組織角色 {{name}} 已成功刪除。', diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts index 7fd35c34a..0fefcb088 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts @@ -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: '輸入你的角色描述', diff --git a/packages/schemas/alterations/next-1719014832-organization-role-types.ts b/packages/schemas/alterations/next-1719014832-organization-role-types.ts new file mode 100644 index 000000000..b46ecc7f7 --- /dev/null +++ b/packages/schemas/alterations/next-1719014832-organization-role-types.ts @@ -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; diff --git a/packages/schemas/src/types/role.ts b/packages/schemas/src/types/role.ts index a2280e33f..aaf225698 100644 --- a/packages/schemas/src/types/role.ts +++ b/packages/schemas/src/types/role.ts @@ -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); diff --git a/packages/schemas/src/types/tenant-organization.ts b/packages/schemas/src/types/tenant-organization.ts index beaf4524a..b6de216e8 100644 --- a/packages/schemas/src/types/tenant-organization.ts +++ b/packages/schemas/src/types/tenant-organization.ts @@ -8,6 +8,7 @@ */ import { + RoleType, type CreateOrganization, type OrganizationRole, type OrganizationScope, @@ -147,6 +148,7 @@ const tenantRoleDescriptions: Readonly> = 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 => id: role, name: role, description: tenantRoleDescriptions[role], + type: RoleType.User, }); /** diff --git a/packages/schemas/tables/organization_role_application_relations.sql b/packages/schemas/tables/organization_role_application_relations.sql index 05f27a52a..c3acc7b94 100644 --- a/packages/schemas/tables/organization_role_application_relations.sql +++ b/packages/schemas/tables/organization_role_application_relations.sql @@ -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')) ); diff --git a/packages/schemas/tables/organization_role_user_relations.sql b/packages/schemas/tables/organization_role_user_relations.sql index 8a6cdc6a7..a432704ef 100644 --- a/packages/schemas/tables/organization_role_user_relations.sql +++ b/packages/schemas/tables/organization_role_user_relations.sql @@ -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')) ); diff --git a/packages/schemas/tables/organization_roles.sql b/packages/schemas/tables/organization_roles.sql index 85b19a7ed..3d7b86efa 100644 --- a/packages/schemas/tables/organization_roles.sql +++ b/packages/schemas/tables/organization_roles.sql @@ -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;