Merge branch 'develop' into staging-new

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2025-05-19 09:51:05 +07:00
commit ae5b768859
No known key found for this signature in database
GPG Key ID: BD80F68D68D8F7F2
11 changed files with 72 additions and 42 deletions

View File

@ -1,5 +1,11 @@
import calendar from '@hcengineering/calendar' import calendar from '@hcengineering/calendar'
import { type PersonId, type WorkspaceInfoWithStatus, type WorkspaceUuid, systemAccountUuid } from '@hcengineering/core' import {
type PersonId,
type WorkspaceInfoWithStatus,
type WorkspaceUuid,
isActiveMode,
systemAccountUuid
} from '@hcengineering/core'
import { getClient as getKvsClient } from '@hcengineering/kvs-client' import { getClient as getKvsClient } from '@hcengineering/kvs-client'
import { createClient, getAccountClient } from '@hcengineering/server-client' import { createClient, getAccountClient } from '@hcengineering/server-client'
import { generateToken } from '@hcengineering/server-token' import { generateToken } from '@hcengineering/server-token'
@ -129,6 +135,7 @@ async function migrateCalendarIntegrations (
if (ws == null) { if (ws == null) {
continue continue
} }
if (!isActiveMode(ws.mode)) continue
token.workspace = ws.uuid token.workspace = ws.uuid
const personId = await getPersonIdByEmail(ws.uuid, token.email, token.userId) const personId = await getPersonIdByEmail(ws.uuid, token.email, token.userId)
@ -215,6 +222,7 @@ async function migrateCalendarHistory (
if (ws == null) { if (ws == null) {
continue continue
} }
if (!isActiveMode(ws.mode)) continue
const personId = await getPersonIdByEmail(ws.uuid, history.email, history.userId) const personId = await getPersonIdByEmail(ws.uuid, history.email, history.userId)
if (personId == null) { if (personId == null) {

View File

@ -32,6 +32,7 @@ import {
type PermissionsStore, type PermissionsStore,
type Person, type Person,
type PersonsByPermission, type PersonsByPermission,
type MembersBySpace,
type SocialIdentity type SocialIdentity
} from '@hcengineering/contact' } from '@hcengineering/contact'
import core, { import core, {
@ -732,19 +733,33 @@ export async function resolveLocationData (loc: Location): Promise<LocationData>
} }
export function checkMyPermission (_id: Ref<Permission>, space: Ref<TypedSpace>, store: PermissionsStore): boolean { export function checkMyPermission (_id: Ref<Permission>, space: Ref<TypedSpace>, store: PermissionsStore): boolean {
const arePermissionsDisabled = getMetadata(core.metadata.DisablePermissions) ?? false
if (arePermissionsDisabled) return true
return (store.whitelist.has(space) || store.ps[space]?.has(_id)) ?? false return (store.whitelist.has(space) || store.ps[space]?.has(_id)) ?? false
} }
export function getPermittedPersons (
_id: Ref<Permission>,
space: Ref<TypedSpace>,
store: PermissionsStore
): Array<Ref<Person>> {
const arePermissionsDisabled = getMetadata(core.metadata.DisablePermissions) ?? false
if (arePermissionsDisabled) return Array.from(store.ms[space] ?? [])
return store.whitelist.has(space) ? Array.from(store.ms[space] ?? []) : Array.from(store.ap[space]?.[_id] ?? [])
}
const spacesStore = writable<Space[]>([]) const spacesStore = writable<Space[]>([])
export const permissionsStore = derived([spacesStore, personRefByAccountUuidStore], ([spaces, personRefByAccount]) => { export const permissionsStore = derived([spacesStore, personRefByAccountUuidStore], ([spaces, personRefByAccount]) => {
const whitelistedSpaces = new Set<Ref<Space>>() const whitelistedSpaces = new Set<Ref<Space>>()
const permissionsBySpace: PermissionsBySpace = {} const permissionsBySpace: PermissionsBySpace = {}
const employeesByPermission: PersonsByPermission = {} const employeesByPermission: PersonsByPermission = {}
const membersBySpace: MembersBySpace = {}
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
for (const s of spaces) { for (const s of spaces) {
membersBySpace[s._id] = new Set(s.members.map((m) => personRefByAccount.get(m)).filter(notEmpty))
if (hierarchy.isDerived(s._class, core.class.TypedSpace)) { if (hierarchy.isDerived(s._class, core.class.TypedSpace)) {
const type = client.getModel().findAllSync(core.class.SpaceType, { _id: (s as TypedSpace).type })[0] const type = client.getModel().findAllSync(core.class.SpaceType, { _id: (s as TypedSpace).type })[0]
const mixin = type?.targetClass const mixin = type?.targetClass
@ -790,6 +805,7 @@ export const permissionsStore = derived([spacesStore, personRefByAccountUuidStor
return { return {
ps: permissionsBySpace, ps: permissionsBySpace,
ap: employeesByPermission, ap: employeesByPermission,
ms: membersBySpace,
whitelist: whitelistedSpaces whitelist: whitelistedSpaces
} }
}) })
@ -815,6 +831,7 @@ spaceTypesQuery.query(core.class.SpaceType, {}, (types) => {
projection: { projection: {
_id: 1, _id: 1,
type: 1, type: 1,
members: 1,
...targetClasses ...targetClasses
} as any } as any
} }

View File

@ -52,9 +52,11 @@ export const AVATAR_COLORS: ColorDefinition[] = [
export type PermissionsBySpace = Record<Ref<Space>, Set<Ref<Permission>>> export type PermissionsBySpace = Record<Ref<Space>, Set<Ref<Permission>>>
export type PersonsByPermission = Record<Ref<Space>, Record<Ref<Permission>, Set<Ref<Person>>>> export type PersonsByPermission = Record<Ref<Space>, Record<Ref<Permission>, Set<Ref<Person>>>>
export type MembersBySpace = Record<Ref<Space>, Set<Ref<Person>>>
export interface PermissionsStore { export interface PermissionsStore {
ps: PermissionsBySpace ps: PermissionsBySpace
ap: PersonsByPermission ap: PersonsByPermission
ms: MembersBySpace
whitelist: Set<Ref<Space>> whitelist: Set<Ref<Space>>
} }

View File

@ -4,18 +4,18 @@
// //
--> -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'
import { RequestStatus } from '@hcengineering/request'
import { Label, ModernDialog, showPopup } from '@hcengineering/ui'
import { getClient } from '@hcengineering/presentation'
import { Employee } from '@hcengineering/contact' import { Employee } from '@hcengineering/contact'
import { Class, Ref } from '@hcengineering/core' import { UserBoxItems, getPermittedPersons, permissionsStore } from '@hcengineering/contact-resources'
import { UserBoxItems, permissionsStore } from '@hcengineering/contact-resources'
import documents, { import documents, {
ControlledDocument, ControlledDocument,
ControlledDocumentState, ControlledDocumentState,
DocumentRequest DocumentRequest
} from '@hcengineering/controlled-documents' } from '@hcengineering/controlled-documents'
import { Class, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { RequestStatus } from '@hcengineering/request'
import { Label, ModernDialog, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import documentsRes from '../plugin' import documentsRes from '../plugin'
import { sendApprovalRequest, sendReviewRequest } from '../utils' import { sendApprovalRequest, sendReviewRequest } from '../utils'
@ -38,7 +38,7 @@
const permissionId = isReviewRequest ? documents.permission.ReviewDocument : documents.permission.ApproveDocument const permissionId = isReviewRequest ? documents.permission.ReviewDocument : documents.permission.ApproveDocument
$: permissionsSpace = $: permissionsSpace =
controlledDoc.space === documents.space.UnsortedTemplates ? documents.space.QualityDocuments : controlledDoc.space controlledDoc.space === documents.space.UnsortedTemplates ? documents.space.QualityDocuments : controlledDoc.space
$: permittedPeople = $permissionsStore.ap[permissionsSpace]?.[permissionId] ?? new Set() $: permittedPeople = new Set(getPermittedPersons(permissionId, permissionsSpace, $permissionsStore))
$: permittedEmployees = Array.from(permittedPeople) as Ref<Employee>[] $: permittedEmployees = Array.from(permittedPeople) as Ref<Employee>[]
let docRequest: DocumentRequest | undefined let docRequest: DocumentRequest | undefined

View File

@ -14,7 +14,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { type Doc, type Ref, type Space } from '@hcengineering/core' import { TypedSpace, type Doc, type Ref, type Space } from '@hcengineering/core'
import documents, { import documents, {
type DocumentSpace, type DocumentSpace,
type DocumentSpaceType, type DocumentSpaceType,
@ -23,7 +23,7 @@
} from '@hcengineering/controlled-documents' } from '@hcengineering/controlled-documents'
import { SpaceSelector, getClient } from '@hcengineering/presentation' import { SpaceSelector, getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui' import { Label } from '@hcengineering/ui'
import { permissionsStore } from '@hcengineering/contact-resources' import { checkMyPermission, permissionsStore } from '@hcengineering/contact-resources'
import { $locationStep as locationStep, locationStepUpdated } from '../../../stores/wizards/create-document' import { $locationStep as locationStep, locationStepUpdated } from '../../../stores/wizards/create-document'
import DocumentParentSelector from '../../hierarchy/DocumentParentSelector.svelte' import DocumentParentSelector from '../../hierarchy/DocumentParentSelector.svelte'
@ -82,9 +82,9 @@
$: canProceed = $locationStep.space !== undefined && $locationStep.project !== undefined $: canProceed = $locationStep.space !== undefined && $locationStep.project !== undefined
$: hasParentSelector = $locationStep.space !== documents.space.UnsortedTemplates $: hasParentSelector = $locationStep.space !== documents.space.UnsortedTemplates
$: restrictedSpaces = Object.entries($permissionsStore.ps) $: restrictedSpaces = Object.keys($permissionsStore.ps).filter(
.filter(([, pss]) => !pss.has(documents.permission.CreateDocument)) (s) => !checkMyPermission(documents.permission.CreateDocument, s as Ref<TypedSpace>, $permissionsStore)
.map(([s]) => s) as Ref<Space>[] ) as Ref<TypedSpace>[]
$: spaceQuery = isTemplate $: spaceQuery = isTemplate
? { _id: { $nin: restrictedSpaces }, archived: false, _class: { $nin: externalSpaces } } ? { _id: { $nin: restrictedSpaces }, archived: false, _class: { $nin: externalSpaces } }

View File

@ -13,12 +13,12 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { getCurrentEmployee, type Employee } from '@hcengineering/contact'
import { Label } from '@hcengineering/ui' import { UserBoxItems, getPermittedPersons, permissionsStore } from '@hcengineering/contact-resources'
import { TypedSpace, type Data, type Ref, type Permission } from '@hcengineering/core'
import { type Employee, type PermissionsStore, getCurrentEmployee } from '@hcengineering/contact'
import documents, { type ControlledDocument } from '@hcengineering/controlled-documents' import documents, { type ControlledDocument } from '@hcengineering/controlled-documents'
import { UserBoxItems, permissionsStore } from '@hcengineering/contact-resources' import { TypedSpace, type Data, type Ref } from '@hcengineering/core'
import { Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let controlledDoc: Data<ControlledDocument> export let controlledDoc: Data<ControlledDocument>
export let space: Ref<TypedSpace> export let space: Ref<TypedSpace>
@ -34,23 +34,23 @@
$: permissionsSpace = space === documents.space.UnsortedTemplates ? documents.space.QualityDocuments : space $: permissionsSpace = space === documents.space.UnsortedTemplates ? documents.space.QualityDocuments : space
function getPermittedPersons ( $: permittedReviewers = getPermittedPersons(
permission: Ref<Permission>, documents.permission.ReviewDocument,
space: Ref<TypedSpace>, permissionsSpace,
permissionsStore: PermissionsStore $permissionsStore
): Ref<Employee>[] { ) as Ref<Employee>[]
return Array.from(permissionsStore.ap[space]?.[permission] ?? []) as Ref<Employee>[]
}
$: permittedReviewers = getPermittedPersons(documents.permission.ReviewDocument, permissionsSpace, $permissionsStore) $: permittedApprovers = getPermittedPersons(
documents.permission.ApproveDocument,
$: permittedApprovers = getPermittedPersons(documents.permission.ApproveDocument, permissionsSpace, $permissionsStore) permissionsSpace,
$permissionsStore
) as Ref<Employee>[]
$: permittedCoAuthors = getPermittedPersons( $: permittedCoAuthors = getPermittedPersons(
documents.permission.CoAuthorDocument, documents.permission.CoAuthorDocument,
permissionsSpace, permissionsSpace,
$permissionsStore $permissionsStore
).filter((person) => person !== currentEmployee) ).filter((person) => person !== currentEmployee) as Ref<Employee>[]
function handleUsersUpdated (type: 'reviewers' | 'approvers' | 'coAuthors', users: Ref<Employee>[]): void { function handleUsersUpdated (type: 'reviewers' | 'approvers' | 'coAuthors', users: Ref<Employee>[]): void {
dispatch('update', { type, users }) dispatch('update', { type, users })

View File

@ -25,10 +25,10 @@
type Project, type Project,
type ProjectDocument type ProjectDocument
} from '@hcengineering/controlled-documents' } from '@hcengineering/controlled-documents'
import { type Doc, type Ref, type Space } from '@hcengineering/core' import { TypedSpace, type Doc, type Ref, type Space } from '@hcengineering/core'
import presentation, { getClient, SpaceSelector } from '@hcengineering/presentation' import presentation, { getClient, SpaceSelector } from '@hcengineering/presentation'
import { Button, Label } from '@hcengineering/ui' import { Button, Label } from '@hcengineering/ui'
import { permissionsStore } from '@hcengineering/contact-resources' import { checkMyPermission, permissionsStore } from '@hcengineering/contact-resources'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import documentsRes from '../../../plugin' import documentsRes from '../../../plugin'
@ -139,9 +139,9 @@
const externalSpaces = hierarchy.getDescendants(documents.class.ExternalSpace) const externalSpaces = hierarchy.getDescendants(documents.class.ExternalSpace)
$: hasParentSelector = targetSpaceId !== documents.space.UnsortedTemplates $: hasParentSelector = targetSpaceId !== documents.space.UnsortedTemplates
$: permissionRestrictedSpaces = Object.entries($permissionsStore.ps) $: permissionRestrictedSpaces = Object.keys($permissionsStore.ps).filter(
.filter(([, pss]) => !pss.has(documents.permission.CreateDocument)) (s) => !checkMyPermission(documents.permission.CreateDocument, s as Ref<TypedSpace>, $permissionsStore)
.map(([s]) => s) as Ref<Space>[] ) as Ref<TypedSpace>[]
$: restrictedSpaces = $: restrictedSpaces =
sourceSpaceId !== undefined ? permissionRestrictedSpaces.concat(sourceSpaceId) : permissionRestrictedSpaces sourceSpaceId !== undefined ? permissionRestrictedSpaces.concat(sourceSpaceId) : permissionRestrictedSpaces

View File

@ -703,7 +703,9 @@ export async function getDocumentMetaTitle (
if (object === undefined) return '' if (object === undefined) return ''
return object.title const hint = await translate(documentsResources.string.LatestVersionHint, {})
return object.title + ` (${hint})`
} }
export async function controlledDocumentReferenceObjectProvider ( export async function controlledDocumentReferenceObjectProvider (

View File

@ -25,7 +25,7 @@ import { getClient } from './client'
import { addUserByEmail, removeUserByEmail } from './kvsUtils' import { addUserByEmail, removeUserByEmail } from './kvsUtils'
import { IncomingSyncManager, lock } from './sync' import { IncomingSyncManager, lock } from './sync'
import { CALENDAR_INTEGRATION, GoogleEmail, SCOPES, State, Token, User } from './types' import { CALENDAR_INTEGRATION, GoogleEmail, SCOPES, State, Token, User } from './types'
import { getGoogleClient, getServiceToken } from './utils' import { getGoogleClient, getWorkspaceToken } from './utils'
import { WatchController } from './watch' import { WatchController } from './watch'
interface AuthResult { interface AuthResult {
@ -58,7 +58,7 @@ export class AuthController {
await ctx.with('Create auth controller', { workspace: state.workspace, user: state.userId }, async () => { await ctx.with('Create auth controller', { workspace: state.workspace, user: state.userId }, async () => {
const mutex = await lock(`${state.workspace}:${state.userId}`) const mutex = await lock(`${state.workspace}:${state.userId}`)
try { try {
const client = await getClient(getServiceToken()) const client = await getClient(getWorkspaceToken(state.workspace))
const txOp = new TxOperations(client, core.account.System) const txOp = new TxOperations(client, core.account.System)
const controller = new AuthController(ctx, accountClient, txOp, state) const controller = new AuthController(ctx, accountClient, txOp, state)
await controller.process(code) await controller.process(code)
@ -78,7 +78,7 @@ export class AuthController {
await ctx.with('Signout auth controller', { workspace, userId }, async () => { await ctx.with('Signout auth controller', { workspace, userId }, async () => {
const mutex = await lock(`${workspace}:${userId}`) const mutex = await lock(`${workspace}:${userId}`)
try { try {
const client = await getClient(getServiceToken()) const client = await getClient(getWorkspaceToken(workspace))
const txOp = new TxOperations(client, core.account.System) const txOp = new TxOperations(client, core.account.System)
const controller = new AuthController(ctx, accountClient, txOp, { const controller = new AuthController(ctx, accountClient, txOp, {
userId, userId,

View File

@ -19,7 +19,7 @@ import { getClient } from './client'
import { getUserByEmail } from './kvsUtils' import { getUserByEmail } from './kvsUtils'
import { IncomingSyncManager } from './sync' import { IncomingSyncManager } from './sync'
import { GoogleEmail, Token } from './types' import { GoogleEmail, Token } from './types'
import { getGoogleClient, getServiceToken } from './utils' import { getGoogleClient, getWorkspaceToken } from './utils'
export class PushHandler { export class PushHandler {
constructor ( constructor (
@ -29,7 +29,7 @@ export class PushHandler {
async sync (token: Token, calendarId: string | null): Promise<void> { async sync (token: Token, calendarId: string | null): Promise<void> {
await this.ctx.with('Push handler', { workspace: token.workspace, user: token.userId }, async () => { await this.ctx.with('Push handler', { workspace: token.workspace, user: token.userId }, async () => {
const client = await getClient(getServiceToken()) const client = await getClient(getWorkspaceToken(token.workspace))
const txOp = new TxOperations(client, core.account.System) const txOp = new TxOperations(client, core.account.System)
const res = getGoogleClient() const res = getGoogleClient()
res.auth.setCredentials(token) res.auth.setCredentials(token)

View File

@ -221,10 +221,11 @@ async function saveMessageToSpaces (
archived: false, archived: false,
createdBy: modifiedBy, createdBy: modifiedBy,
modifiedBy, modifiedBy,
parent: channel parent: channel,
createdOn: createdDate
}, },
generateId(), generateId(),
undefined, createdDate,
modifiedBy modifiedBy
) )
await client.createMixin( await client.createMixin(