From 2c2a217b1edeeffbe9cf3f2c793d99d50d4d3c07 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sat, 29 Nov 2025 11:19:46 +0500 Subject: [PATCH] Improve cards permissions (#10260) Signed-off-by: Denis Bykhov --- common/scripts/version.txt | 2 +- foundations/core/packages/core/src/classes.ts | 4 ++ .../core/packages/core/src/component.ts | 4 +- models/card/src/index.ts | 6 ++- models/card/src/migration.ts | 30 ++++++++++---- models/core/src/index.ts | 12 +++++- models/core/src/security.ts | 10 ++++- .../components/navigator/CreateSpace.svelte | 3 +- .../navigator/SpacePresenter.svelte | 4 +- .../sections/RelationsSection.svelte | 2 +- .../settings/CreateRolePopup.svelte | 2 +- .../src/components/settings/EditRole.svelte | 39 ++++++++++++++---- .../src/components/settings/RolesPopup.svelte | 40 +++++++++++++++++++ .../components/settings/RolesSection.svelte | 25 ++++++++++-- plugins/card-resources/src/index.ts | 4 +- plugins/card-resources/src/plugin.ts | 3 +- plugins/card/src/index.ts | 2 +- .../src/components/EditAttribute.svelte | 16 ++++---- 18 files changed, 171 insertions(+), 37 deletions(-) create mode 100644 plugins/card-resources/src/components/settings/RolesPopup.svelte diff --git a/common/scripts/version.txt b/common/scripts/version.txt index bb88736814..865098066d 100644 --- a/common/scripts/version.txt +++ b/common/scripts/version.txt @@ -1 +1 @@ -"0.7.301" +"0.7.312" diff --git a/foundations/core/packages/core/src/classes.ts b/foundations/core/packages/core/src/classes.ts index 8e72944b72..359e49e681 100644 --- a/foundations/core/packages/core/src/classes.ts +++ b/foundations/core/packages/core/src/classes.ts @@ -551,6 +551,10 @@ export interface Permission extends Doc { icon?: Asset } +export interface AttributePermission extends Permission { + attribute: Ref +} + /** * @public */ diff --git a/foundations/core/packages/core/src/component.ts b/foundations/core/packages/core/src/component.ts index 441c27ed75..439ab5d827 100644 --- a/foundations/core/packages/core/src/component.ts +++ b/foundations/core/packages/core/src/component.ts @@ -63,7 +63,8 @@ import type { Version, AccountUuid, ClassCollaborators, - Collaborator + Collaborator, + AttributePermission } from './classes' import { type Status, type StatusCategory } from './status' import type { @@ -129,6 +130,7 @@ export default plugin(coreId, { SpaceType: '' as Ref>, Role: '' as Ref>, Permission: '' as Ref>, + AttributePermission: '' as Ref>, Type: '' as Ref>>, TypeRelation: '' as Ref>>, TypeString: '' as Ref>>, diff --git a/models/card/src/index.ts b/models/card/src/index.ts index fa6539429f..f590c18cf3 100644 --- a/models/card/src/index.ts +++ b/models/card/src/index.ts @@ -177,7 +177,7 @@ export class TCardViewDefaults extends TMasterTag implements CardViewDefaults { @Model(card.class.Role, core.class.Role, DOMAIN_MODEL) export class TRole extends TBaseRole implements Role { - type!: Ref + types!: Ref[] } @Model(card.class.FavoriteCard, preference.class.Preference) @@ -901,6 +901,10 @@ function defineTabs (builder: Builder): void { } }) + builder.mixin(card.class.Role, core.class.Class, view.mixin.ObjectPresenter, { + presenter: setting.component.PermissionPresenter + }) + builder.createDoc(view.class.Viewlet, core.space.Model, { attachTo: card.class.FavoriteCard, descriptor: view.viewlet.List, diff --git a/models/card/src/migration.ts b/models/card/src/migration.ts index 79cad9bef7..4cd6e2c0df 100644 --- a/models/card/src/migration.ts +++ b/models/card/src/migration.ts @@ -13,27 +13,27 @@ // limitations under the License. // -import cardPlugin, { type Card, cardId, DOMAIN_CARD, type Role } from '@hcengineering/card' +import cardPlugin, { cardId, DOMAIN_CARD, type Card, type Role } from '@hcengineering/card' import core, { DOMAIN_MODEL, - type Ref, TxOperations, type Client, type Data, type Doc, - type DocumentUpdate + type DocumentUpdate, + type Ref } from '@hcengineering/core' import { + createOrUpdate, tryMigrate, tryUpgrade, type MigrateOperation, type MigrationClient, - type MigrationUpgradeClient, - createOrUpdate + type MigrationUpgradeClient } from '@hcengineering/model' +import tags from '@hcengineering/tags' import view, { type Viewlet } from '@hcengineering/view' import card from '.' -import tags from '@hcengineering/tags' export const cardOperation: MigrateOperation = { async migrate (client: MigrationClient, mode): Promise { @@ -100,6 +100,11 @@ export const cardOperation: MigrateOperation = { state: 'add-space-type', mode: 'upgrade', func: addSpaceType + }, + { + state: 'migrate-role-types', + mode: 'upgrade', + func: migrateRoleTypes } ]) } @@ -118,7 +123,7 @@ async function migrateRolesToBaseRole (client: MigrationUpgradeClient): Promise< const roles = await client.findAll(card.class.Role, { attachedTo: { $ne: cardPlugin.spaceType.SpaceType } }) for (const role of roles) { const baseRoleData: DocumentUpdate = { - type: role.attachedTo as any, + types: [role.attachedTo as any], attachedTo: cardPlugin.spaceType.SpaceType, attachedToClass: core.class.SpaceType } @@ -383,3 +388,14 @@ async function makeConfigSortable (client: Client): Promise { await txOp.update(currentViewlet, { configOptions }) } } + +async function migrateRoleTypes (client: Client): Promise { + const txOp = new TxOperations(client, core.account.System) + const roles = await client.findAll(card.class.Role, { types: { $exists: false } }) + for (const role of roles) { + const baseRoleData: DocumentUpdate = { + types: [(role as any).type] + } + await txOp.update(role, baseRoleData) + } +} diff --git a/models/core/src/index.ts b/models/core/src/index.ts index c49249021a..314b3f6e99 100644 --- a/models/core/src/index.ts +++ b/models/core/src/index.ts @@ -76,7 +76,16 @@ import { TCollaborator } from './core' import { definePermissions } from './permissions' -import { TPermission, TRole, TSpace, TSpaceType, TSpaceTypeDescriptor, TSystemSpace, TTypedSpace } from './security' +import { + TAttributePermission, + TPermission, + TRole, + TSpace, + TSpaceType, + TSpaceTypeDescriptor, + TSystemSpace, + TTypedSpace +} from './security' import { defineSpaceType } from './spaceType' import { TDomainStatusPlaceholder, TStatus, TStatusCategory } from './status' import { TUserStatus } from './transient' @@ -124,6 +133,7 @@ export function createModel (builder: Builder): void { TSpaceTypeDescriptor, TRole, TPermission, + TAttributePermission, TAttribute, TType, TEnumOf, diff --git a/models/core/src/security.ts b/models/core/src/security.ts index c3c9a2eda4..4dcfc2107a 100644 --- a/models/core/src/security.ts +++ b/models/core/src/security.ts @@ -31,7 +31,9 @@ import { type AccountUuid, type TxAccessLevel, type Tx, - type Doc + type Doc, + type AnyAttribute, + type AttributePermission } from '@hcengineering/core' import { ArrOf, @@ -165,6 +167,12 @@ export class TPermission extends TDoc implements Permission { icon?: Asset } +@Model(core.class.AttributePermission, core.class.Permission) +@UX(core.string.Permission) +export class TAttributePermission extends TPermission implements AttributePermission { + attribute!: Ref +} + @Mixin(core.mixin.SpacesTypeData, core.class.Space) @UX(getEmbeddedLabel("All spaces' type")) // TODO: add icon? export class TSpacesTypeData extends TSpace implements RolesAssignment { diff --git a/plugins/card-resources/src/components/navigator/CreateSpace.svelte b/plugins/card-resources/src/components/navigator/CreateSpace.svelte index d6a343d5f7..286cae42c1 100644 --- a/plugins/card-resources/src/components/navigator/CreateSpace.svelte +++ b/plugins/card-resources/src/components/navigator/CreateSpace.svelte @@ -32,7 +32,8 @@ const hierarchy = client.getHierarchy() const topLevelTypes = client.getModel().findAllSync(card.class.MasterTag, { - extends: card.class.Card + extends: card.class.Card, + removed: { $ne: true } }) let types: Ref[] = diff --git a/plugins/card-resources/src/components/navigator/SpacePresenter.svelte b/plugins/card-resources/src/components/navigator/SpacePresenter.svelte index 92fd8946b8..4c70cd6eaa 100644 --- a/plugins/card-resources/src/components/navigator/SpacePresenter.svelte +++ b/plugins/card-resources/src/components/navigator/SpacePresenter.svelte @@ -22,12 +22,13 @@ import { CardSpace, MasterTag } from '@hcengineering/card' import card from '../../plugin' import { onDestroy } from 'svelte' - import { location } from '@hcengineering/ui' + import { Action, location } from '@hcengineering/ui' export let space: CardSpace export let model: SpacesNavModel export let currentSpace: Ref | undefined export let forciblyСollapsed: boolean = false + export let getActions: (space: Space) => Promise = async () => [] let classes: MasterTag[] = [] let allClasses: MasterTag[] = [] @@ -61,6 +62,7 @@ highlighted={currentSpace === space._id} visible={currentSpace === space._id || forciblyСollapsed} {forciblyСollapsed} + actions={() => getActions(space)} on:dragstart={(evt) => { evt.preventDefault() }} diff --git a/plugins/card-resources/src/components/sections/RelationsSection.svelte b/plugins/card-resources/src/components/sections/RelationsSection.svelte index 6d3573377e..4b6cc4c1ca 100644 --- a/plugins/card-resources/src/components/sections/RelationsSection.svelte +++ b/plugins/card-resources/src/components/sections/RelationsSection.svelte @@ -22,7 +22,7 @@ export let hidden: boolean = false -{#if hidden} +{#if !hidden}
diff --git a/plugins/card-resources/src/components/settings/CreateRolePopup.svelte b/plugins/card-resources/src/components/settings/CreateRolePopup.svelte index 600ab60467..96040dc981 100644 --- a/plugins/card-resources/src/components/settings/CreateRolePopup.svelte +++ b/plugins/card-resources/src/components/settings/CreateRolePopup.svelte @@ -36,7 +36,7 @@ 'roles', { name: value, - type: masterTag._id, + types: [masterTag._id], permissions: [] } ) diff --git a/plugins/card-resources/src/components/settings/EditRole.svelte b/plugins/card-resources/src/components/settings/EditRole.svelte index 563ef7301f..6f8f66967e 100644 --- a/plugins/card-resources/src/components/settings/EditRole.svelte +++ b/plugins/card-resources/src/components/settings/EditRole.svelte @@ -13,6 +13,7 @@ // limitations under the License. --> + + + +
+ {doc.name} +
+
+
diff --git a/plugins/card-resources/src/components/settings/RolesSection.svelte b/plugins/card-resources/src/components/settings/RolesSection.svelte index d433a6ff8a..a79f9e8b57 100644 --- a/plugins/card-resources/src/components/settings/RolesSection.svelte +++ b/plugins/card-resources/src/components/settings/RolesSection.svelte @@ -20,7 +20,7 @@ import { clearSettingsStore } from '@hcengineering/setting-resources' import { ButtonIcon, getCurrentResolvedLocation, Icon, IconAdd, Label, navigate, showPopup } from '@hcengineering/ui' import card from '../../plugin' - import CreateRolePopup from './CreateRolePopup.svelte' + import RolesPopup from './RolesPopup.svelte' export let masterTag: MasterTag | Tag @@ -28,14 +28,31 @@ const ancestors = client.getHierarchy().getAncestors(masterTag._id) - let roles = client.getModel().findAllSync(card.class.Role, { type: { $in: ancestors } }) + let roles = client.getModel().findAllSync(card.class.Role, { types: { $in: ancestors } }) const query = createQuery() - query.query(card.class.Role, { type: { $in: ancestors } }, (res) => { + query.query(card.class.Role, { types: { $in: ancestors } }, (res) => { roles = res }) function addRole (): void { - showPopup(CreateRolePopup, { masterTag }) + showPopup( + RolesPopup, + { + masterTag, + roles: roles.map((r) => r._id) + }, + 'top', + async (res) => { + if (res !== undefined) { + const role = res as Role + if (!role.types.some((type) => ancestors.includes(type))) { + await client.update(role, { + $push: { types: masterTag._id } + }) + } + } + } + ) } const handleSelect = (role: Role): void => { diff --git a/plugins/card-resources/src/index.ts b/plugins/card-resources/src/index.ts index 6020da6754..7f949f11ed 100644 --- a/plugins/card-resources/src/index.ts +++ b/plugins/card-resources/src/index.ts @@ -58,6 +58,7 @@ import TypesNavigator from './components/navigator/TypesNavigator.svelte' import LabelsPresenter from './components/LabelsPresenter.svelte' import RolesSection from './components/settings/RolesSection.svelte' import EditRole from './components/settings/EditRole.svelte' +import CreateRolePopup from './components/settings/CreateRolePopup.svelte' import CardWidget from './components/CardWidget.svelte' import CreateSpace from './components/navigator/CreateSpace.svelte' import CardHeaderButton from './components/navigator/CardHeaderButton.svelte' @@ -127,7 +128,8 @@ export default async (): Promise => ({ CardIcon, CardFeedView, CreateSpace, - CardHeaderButton + CardHeaderButton, + CreateRolePopup }, sectionComponent: { AttachmentsSection: AttachmentsCardSection, diff --git a/plugins/card-resources/src/plugin.ts b/plugins/card-resources/src/plugin.ts index b86ba50f5c..bce1326065 100644 --- a/plugins/card-resources/src/plugin.ts +++ b/plugins/card-resources/src/plugin.ts @@ -53,7 +53,8 @@ export default mergeIds(cardId, card, { CardWidget: '' as AnyComponent, CardWidgetTab: '' as AnyComponent, CreateCard: '' as AnyComponent, - CardHeaderButton: '' as AnyComponent + CardHeaderButton: '' as AnyComponent, + CreateRolePopup: '' as AnyComponent }, function: { CardFactory: '' as Resource<(props?: Record) => Promise | undefined>> diff --git a/plugins/card/src/index.ts b/plugins/card/src/index.ts index 52dc389ab4..08ef2048aa 100644 --- a/plugins/card/src/index.ts +++ b/plugins/card/src/index.ts @@ -44,7 +44,7 @@ export interface MasterTag extends Class { export interface Tag extends MasterTag, Mixin {} export interface Role extends BaseRole { - type: Ref + types: Ref[] } export interface Card extends Doc, IconProps { diff --git a/plugins/setting-resources/src/components/EditAttribute.svelte b/plugins/setting-resources/src/components/EditAttribute.svelte index 4c88629754..491675f924 100644 --- a/plugins/setting-resources/src/components/EditAttribute.svelte +++ b/plugins/setting-resources/src/components/EditAttribute.svelte @@ -15,10 +15,10 @@