diff --git a/models/attachment/src/migration.ts b/models/attachment/src/migration.ts index df8c59ae94..8b733db333 100644 --- a/models/attachment/src/migration.ts +++ b/models/attachment/src/migration.ts @@ -31,9 +31,7 @@ export const attachmentOperation: MigrateOperation = { DOMAIN_ATTACHMENT, { _class: attachment.class.Attachment, attachedToClass: 'chunter:class:Comment' }, { - $set: { - attachedToClass: 'chunter:class:ChatMessage' - } + attachedToClass: 'chunter:class:ChatMessage' } ) } diff --git a/models/contact/src/migration.ts b/models/contact/src/migration.ts index 2efb5bfb49..c9b74529a7 100644 --- a/models/contact/src/migration.ts +++ b/models/contact/src/migration.ts @@ -168,7 +168,7 @@ export const contactOperation: MigrateOperation = { objectClass: 'contact:class:Employee' }, { - $set: { objectClass: contact.mixin.Employee } + objectClass: contact.mixin.Employee } ) @@ -178,7 +178,7 @@ export const contactOperation: MigrateOperation = { 'tx.attributes.srcDocClass': 'contact:class:Employee' }, { - $set: { 'tx.attributes.srcDocClass': contact.mixin.Employee } + 'tx.attributes.srcDocClass': contact.mixin.Employee } ) @@ -188,7 +188,7 @@ export const contactOperation: MigrateOperation = { 'tx.attributes.srcDocClass': 'contact:class:Employee' }, { - $set: { 'tx.attributes.srcDocClass': contact.mixin.Employee } + 'tx.attributes.srcDocClass': contact.mixin.Employee } ) @@ -199,7 +199,7 @@ export const contactOperation: MigrateOperation = { 'attributes.type.to': 'contact:class:Employee' }, { - $set: { 'attributes.type.to': contact.mixin.Employee } + 'attributes.type.to': contact.mixin.Employee } ) await client.update( @@ -209,7 +209,7 @@ export const contactOperation: MigrateOperation = { 'operations.type.to': 'contact:class:Employee' }, { - $set: { 'operations.type.to': contact.mixin.Employee } + 'operations.type.to': contact.mixin.Employee } ) @@ -219,7 +219,7 @@ export const contactOperation: MigrateOperation = { 'attributes.extends': 'contact:class:Employee' }, { - $set: { 'attributes.extends': contact.mixin.Employee } + 'attributes.extends': contact.mixin.Employee } ) @@ -227,7 +227,7 @@ export const contactOperation: MigrateOperation = { await client.update( d, { attachedToClass: 'contact:class:Employee' }, - { $set: { attachedToClass: contact.mixin.Employee } } + { attachedToClass: contact.mixin.Employee } ) } await client.update( @@ -236,17 +236,17 @@ export const contactOperation: MigrateOperation = { _class: activity.class.ActivityReference, srcDocClass: 'contact:class:Employee' }, - { $set: { srcDocClass: contact.mixin.Employee } } + { srcDocClass: contact.mixin.Employee } ) await client.update( 'tags' as Domain, { targetClass: 'contact:class:Employee' }, - { $set: { targetClass: contact.mixin.Employee } } + { targetClass: contact.mixin.Employee } ) await client.update( DOMAIN_VIEW, { filterClass: 'contact:class:Employee' }, - { $set: { filterClass: contact.mixin.Employee } } + { filterClass: contact.mixin.Employee } ) await client.update( DOMAIN_CONTACT, @@ -260,9 +260,7 @@ export const contactOperation: MigrateOperation = { displayName: `${contact.mixin.Employee as string}.displayName`, position: `${contact.mixin.Employee as string}.position` }, - $set: { - _class: contact.class.Person - } + _class: contact.class.Person } ) } diff --git a/models/controlled-documents/src/migration.ts b/models/controlled-documents/src/migration.ts index 6900e6c998..0ea98cfb04 100644 --- a/models/controlled-documents/src/migration.ts +++ b/models/controlled-documents/src/migration.ts @@ -270,9 +270,7 @@ async function migrateSpaceTypes (client: MigrationClient): Promise { 'attributes.descriptor': documents.descriptor.DocumentSpaceType }, { - $set: { - objectClass: documents.class.DocumentSpaceType - } + objectClass: documents.class.DocumentSpaceType } ) } @@ -398,7 +396,7 @@ async function migrateProjectMetaRank (client: MigrationClient): Promise { for (const doc of projectMeta) { operations.push({ filter: { _id: doc._id }, - update: { $set: { rank } } + update: { rank } }) rank = makeRank(rank, undefined) } diff --git a/models/core/src/migration.ts b/models/core/src/migration.ts index 6d1d84558d..39e74284e3 100644 --- a/models/core/src/migration.ts +++ b/models/core/src/migration.ts @@ -113,10 +113,8 @@ async function migrateAllSpaceToTyped (client: MigrationClient): Promise { _class: core.class.Space }, { - $set: { - _class: core.class.TypedSpace, - type: core.spaceType.SpacesType - } + _class: core.class.TypedSpace, + type: core.spaceType.SpacesType } ) } @@ -135,9 +133,7 @@ async function migrateSpacesOwner (client: MigrationClient): Promise { _id: space._id }, { - $set: { - owners: [space.createdBy] - } + owners: [space.createdBy] } ) } @@ -669,7 +665,7 @@ export const coreOperation: MigrateOperation = { func: async (client: MigrationClient): Promise => { const now = Date.now().toString(16) for (const d of client.hierarchy.domains()) { - await client.update(d, { '%hash%': { $in: [null, ''] } }, { $set: { '%hash%': now } }) + await client.update(d, { '%hash%': { $in: [null, ''] } }, { '%hash%': now }) } } }, diff --git a/models/document/src/migration.ts b/models/document/src/migration.ts index 0cc53d19b2..cba17b93c6 100644 --- a/models/document/src/migration.ts +++ b/models/document/src/migration.ts @@ -77,9 +77,7 @@ async function migrateTeamspaces (client: MigrationClient): Promise { type: { $exists: false } }, { - $set: { - type: document.spaceType.DefaultTeamspaceType - } + type: document.spaceType.DefaultTeamspaceType } ) } @@ -95,9 +93,7 @@ async function migrateTeamspacesMixins (client: MigrationClient): Promise 'attributes.attributeOf': oldSpaceTypeMixin }, { - $set: { - 'attributes.attributeOf': newSpaceTypeMixin - } + 'attributes.attributeOf': newSpaceTypeMixin } ) @@ -131,7 +127,7 @@ async function migrateRank (client: MigrationClient): Promise { for (const doc of documents) { operations.push({ filter: { _id: doc._id }, - update: { $set: { rank } } + update: { rank } }) rank = makeRank(rank, undefined) } diff --git a/models/drive/src/migration.ts b/models/drive/src/migration.ts index e2762c8b13..59829f2ec1 100644 --- a/models/drive/src/migration.ts +++ b/models/drive/src/migration.ts @@ -73,11 +73,9 @@ async function migrateFileVersions (client: MigrationClient): Promise { _class: file._class }, { - $set: { - version: 1, - versions: 1, - file: fileVersionId - }, + version: 1, + versions: 1, + file: fileVersionId, $unset: { metadata: 1 } diff --git a/models/lead/src/migration.ts b/models/lead/src/migration.ts index 4d13347cd5..98ae6106bf 100644 --- a/models/lead/src/migration.ts +++ b/models/lead/src/migration.ts @@ -116,9 +116,7 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise 'attributes.attributeOf': oldSpaceTypeMixin }, { - $set: { - 'attributes.attributeOf': newSpaceTypeMixin - } + 'attributes.attributeOf': newSpaceTypeMixin } ) diff --git a/models/recruit/src/migration.ts b/models/recruit/src/migration.ts index 21c5c15633..c1e82a38e8 100644 --- a/models/recruit/src/migration.ts +++ b/models/recruit/src/migration.ts @@ -155,9 +155,7 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise 'attributes.attributeOf': oldSpaceTypeMixin }, { - $set: { - 'attributes.attributeOf': newSpaceTypeMixin - } + 'attributes.attributeOf': newSpaceTypeMixin } ) diff --git a/models/task/src/migration.ts b/models/task/src/migration.ts index 4a868f19e6..196268b34a 100644 --- a/models/task/src/migration.ts +++ b/models/task/src/migration.ts @@ -138,9 +138,7 @@ export async function migrateDefaultStatusesBase ( DOMAIN_MODEL_TX, { _id: defaultTaskType._id }, { - $set: { - modifiedBy: core.account.System - } + modifiedBy: core.account.System } ) @@ -148,9 +146,7 @@ export async function migrateDefaultStatusesBase ( DOMAIN_MODEL_TX, { _id: defaultType._id }, { - $set: { - modifiedBy: core.account.System - } + modifiedBy: core.account.System } ) } else if (defaultTaskType?.modifiedBy !== core.account.System) { @@ -178,10 +174,8 @@ export async function migrateDefaultStatusesBase ( DOMAIN_MODEL_TX, { _id: defaultType._id }, { - $set: { - 'attributes.name': defaultType.attributes.name + ' (custom)', - objectId: newId - } + 'attributes.name': defaultType.attributes.name + ' (custom)', + objectId: newId } ) await client.update( @@ -191,9 +185,7 @@ export async function migrateDefaultStatusesBase ( objectSpace: core.space.Model }, { - $set: { - objectId: newId - } + objectId: newId } ) await client.update( @@ -204,9 +196,7 @@ export async function migrateDefaultStatusesBase ( 'attributes.parent': defaultTypeId }, { - $set: { - 'attributes.parent': newId - } + 'attributes.parent': newId } ) await client.update( @@ -216,7 +206,7 @@ export async function migrateDefaultStatusesBase ( type: defaultTypeId }, { - $set: { type: newId } + type: newId } ) } @@ -315,7 +305,7 @@ export async function migrateDefaultStatusesBase ( } counter++ - await client.update(DOMAIN_MODEL_TX, { _id: ptsCreate._id }, { $set: { 'attributes.statuses': newUpdateStatuses } }) + await client.update(DOMAIN_MODEL_TX, { _id: ptsCreate._id }, { 'attributes.statuses': newUpdateStatuses }) } logger.log('projectTypeStatusesCreates updated: ', counter) @@ -337,7 +327,7 @@ export async function migrateDefaultStatusesBase ( } counter++ - await client.update(DOMAIN_MODEL_TX, { _id: ptsUpdate._id }, { $set: { 'operations.statuses': newUpdateStatuses } }) + await client.update(DOMAIN_MODEL_TX, { _id: ptsUpdate._id }, { 'operations.statuses': newUpdateStatuses }) } logger.log('projectTypeStatusesUpdates updated: ', counter) @@ -365,11 +355,7 @@ export async function migrateDefaultStatusesBase ( } counter++ - await client.update( - DOMAIN_MODEL_TX, - { _id: ptsUpdate._id }, - { $set: { 'operations.$push.statuses': newPushStatus } } - ) + await client.update(DOMAIN_MODEL_TX, { _id: ptsUpdate._id }, { 'operations.$push.statuses': newPushStatus }) } logger.log('projectTypeStatusesPushes updated: ', counter) @@ -394,11 +380,7 @@ export async function migrateDefaultStatusesBase ( } counter++ - await client.update( - DOMAIN_MODEL_TX, - { _id: taskType._id }, - { $set: { 'attributes.statuses': newTaskTypeStatuses } } - ) + await client.update(DOMAIN_MODEL_TX, { _id: taskType._id }, { 'attributes.statuses': newTaskTypeStatuses }) } logger.log('allTaskTypes updated: ', counter) @@ -423,11 +405,7 @@ export async function migrateDefaultStatusesBase ( } counter++ - await client.update( - DOMAIN_MODEL_TX, - { _id: ttsUpdate._id }, - { $set: { 'operations.statuses': newTaskTypeUpdateStatuses } } - ) + await client.update(DOMAIN_MODEL_TX, { _id: ttsUpdate._id }, { 'operations.statuses': newTaskTypeUpdateStatuses }) } logger.log('allTaskTypeStatusesUpdates updated: ', counter) @@ -452,7 +430,7 @@ export async function migrateDefaultStatusesBase ( if (newStatus !== baseTask.status) { counter++ - await client.update(DOMAIN_TASK, { _id: baseTask._id }, { $set: { status: newStatus } }) + await client.update(DOMAIN_TASK, { _id: baseTask._id }, { status: newStatus }) } } logger.log('affectedBaseTasks updated: ', counter) @@ -474,11 +452,7 @@ export async function migrateDefaultStatusesBase ( if (statusSet !== newStatusSet) { counter++ - await client.update( - DOMAIN_ACTIVITY, - { _id: updateMessage._id }, - { $set: { 'attributeUpdates.set.0': newStatusSet } } - ) + await client.update(DOMAIN_ACTIVITY, { _id: updateMessage._id }, { 'attributeUpdates.set.0': newStatusSet }) } } logger.log('Base task update messages updated: ', counter) @@ -490,7 +464,7 @@ export async function migrateDefaultStatusesBase ( logger.log('Updating status from ' + statusIdBeingMigrated + ' to ' + newStatus, '') - await client.update(DOMAIN_STATUS, { _id: statusIdBeingMigrated }, { $set: { __superseded: true } }) + await client.update(DOMAIN_STATUS, { _id: statusIdBeingMigrated }, { __superseded: true }) if (!createdStatuses.has(newStatus)) { const oldStatus = oldStatuses.find((s) => s._id === statusIdBeingMigrated) @@ -554,12 +528,12 @@ export const taskOperation: MigrateOperation = { await client.update( DOMAIN_TX, { objectId: { $in: missing }, objectSpace: 'task:space:Statuses' }, - { $set: { objectSpace: core.space.Model } } + { objectSpace: core.space.Model } ) await client.update( DOMAIN_MODEL_TX, { objectId: { $in: missing }, objectSpace: 'task:space:Statuses' }, - { $set: { objectSpace: core.space.Model } } + { objectSpace: core.space.Model } ) await client.move(DOMAIN_TX, { objectId: { $in: missing }, objectSpace: core.space.Model }, DOMAIN_MODEL_TX) } diff --git a/models/tracker/src/migration.ts b/models/tracker/src/migration.ts index b6386fae04..17cca7e59e 100644 --- a/models/tracker/src/migration.ts +++ b/models/tracker/src/migration.ts @@ -152,7 +152,7 @@ async function passIdentifierToParentInfo (client: MigrationClient): Promise { const project = projectsMap.get(issue.space) if (project === undefined) continue const identifier = project.identifier + '-' + issue.number - await client.update(DOMAIN_TASK, { _id: issue._id }, { $set: { identifier } }) + await client.update(DOMAIN_TASK, { _id: issue._id }, { identifier }) } } @@ -203,7 +203,7 @@ async function migrateDefaultStatuses (client: MigrationClient, logger: ModelLog const newDefaultIssueStatus = getNewStatus(project.defaultIssueStatus) if (project.defaultIssueStatus !== newDefaultIssueStatus) { - await client.update(DOMAIN_SPACE, { _id: project._id }, { $set: { defaultIssueStatus: newDefaultIssueStatus } }) + await client.update(DOMAIN_SPACE, { _id: project._id }, { defaultIssueStatus: newDefaultIssueStatus }) } const projectUpdateMessages = await client.find(DOMAIN_ACTIVITY, { @@ -218,11 +218,7 @@ async function migrateDefaultStatuses (client: MigrationClient, logger: ModelLog const newStatusSet = statusSet != null ? getNewStatus(statusSet as Ref) : statusSet if (statusSet !== newStatusSet) { - await client.update( - DOMAIN_ACTIVITY, - { _id: updateMessage._id }, - { $set: { 'attributeUpdates.set.0': newStatusSet } } - ) + await client.update(DOMAIN_ACTIVITY, { _id: updateMessage._id }, { 'attributeUpdates.set.0': newStatusSet }) } } } @@ -300,9 +296,7 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise 'attributes.attributeOf': oldSpaceTypeMixin }, { - $set: { - 'attributes.attributeOf': newSpaceTypeMixin - } + 'attributes.attributeOf': newSpaceTypeMixin } ) @@ -342,9 +336,7 @@ async function migrateIssueStatuses (client: MigrationClient): Promise { 'attributes.statusClass': core.class.Status }, { - $set: { - 'attributes.statusClass': tracker.class.IssueStatus - } + 'attributes.statusClass': tracker.class.IssueStatus } ) await client.update( @@ -354,9 +346,7 @@ async function migrateIssueStatuses (client: MigrationClient): Promise { 'attributes.ofAttribute': tracker.attribute.IssueStatus }, { - $set: { - objectClass: tracker.class.IssueStatus - } + objectClass: tracker.class.IssueStatus } ) @@ -367,9 +357,7 @@ async function migrateIssueStatuses (client: MigrationClient): Promise { ofAttribute: tracker.attribute.IssueStatus }, { - $set: { - _class: tracker.class.IssueStatus - } + _class: tracker.class.IssueStatus } ) } diff --git a/packages/api-client/src/types.ts b/packages/api-client/src/types.ts index 29f94dc81c..4a7cd0a66a 100644 --- a/packages/api-client/src/types.ts +++ b/packages/api-client/src/types.ts @@ -16,7 +16,6 @@ import { type ClientSocketFactory } from '@hcengineering/client' import { CollaborativeDoc, - type Account, type AttachedData, type AttachedDoc, type Class, diff --git a/packages/model/src/migration.ts b/packages/model/src/migration.ts index 4bb594eea9..b514ce1020 100644 --- a/packages/model/src/migration.ts +++ b/packages/model/src/migration.ts @@ -1,3 +1,4 @@ +import { AccountClient } from '@hcengineering/account-client' import { Analytics } from '@hcengineering/analytics' import core, { Class, @@ -10,11 +11,9 @@ import core, { Domain, FindOptions, Hierarchy, - IncOptions, MigrationState, ModelDb, ObjQueryType, - PushOptions, Rank, Ref, SortingOrder, @@ -25,18 +24,13 @@ import core, { generateId } from '@hcengineering/core' import { makeRank } from '@hcengineering/rank' -import { AccountClient } from '@hcengineering/account-client' import { StorageAdapter } from '@hcengineering/storage' import { ModelLogger } from './utils' /** * @public */ -export type MigrateUpdate = Partial & -PushOptions & -IncOptions & -UnsetOptions & -Record +export type MigrateUpdate = Partial & UnsetOptions & Record /** * @public diff --git a/packages/platform-rig/profiles/ui/tsconfig.json b/packages/platform-rig/profiles/ui/tsconfig.json index e095e09e86..5a81153571 100644 --- a/packages/platform-rig/profiles/ui/tsconfig.json +++ b/packages/platform-rig/profiles/ui/tsconfig.json @@ -17,7 +17,12 @@ "dom" ], "incremental": true, - "types": ["jest"], + "types": [ + "jest" + ], "isolatedModules": true - } + }, + "exclude": [ + "node_modules/**" + ] } \ No newline at end of file diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index aacb4b3156..b6471cf6f9 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -745,9 +745,9 @@ export function decodeTokenPayload (token: string): any { } export function isAdminUser (): boolean { - // TODO: fixme - return false - // return decodeTokenPayload(getMetadata(plugin.metadata.Token) ?? '').admin === 'true' + const decodedToken = decodeTokenPayload(getMetadata(plugin.metadata.Token) ?? '') + console.log('decodedToken', decodedToken) + return decodedToken.extra?.admin === 'true' } export function isSpace (space: Doc): space is Space { diff --git a/plugins/login-resources/src/components/AdminWorkspaces.svelte b/plugins/login-resources/src/components/AdminWorkspaces.svelte index 2452fa282b..01724b405a 100644 --- a/plugins/login-resources/src/components/AdminWorkspaces.svelte +++ b/plugins/login-resources/src/components/AdminWorkspaces.svelte @@ -9,7 +9,7 @@ isUpgradingMode, reduceCalls, versionToString, - type BaseWorkspaceInfo + type WorkspaceInfoWithStatus } from '@hcengineering/core' import { getEmbeddedLabel } from '@hcengineering/platform' import { isAdminUser, MessageBox } from '@hcengineering/presentation' @@ -30,7 +30,8 @@ ticker } from '@hcengineering/ui' import { workbenchId } from '@hcengineering/workbench' - import { getAllWorkspaces, getRegionInfo, performWorkspaceOperation, type RegionInfo } from '../utils' + import { getAllWorkspaces, getRegionInfo, performWorkspaceOperation } from '../utils' + import { RegionInfo } from '@hcengineering/account-client' $: now = $ticker @@ -43,7 +44,7 @@ window.open(url, '_blank') } - type WorkspaceInfo = BaseWorkspaceInfo & { attempts: number } + type WorkspaceInfo = WorkspaceInfoWithStatus & { attempts: number } let workspaces: WorkspaceInfo[] = [] @@ -73,9 +74,9 @@ $: sortedWorkspaces = workspaces .filter( (it) => - ((it.workspaceName?.includes(search) ?? false) || - (it.workspaceUrl?.includes(search) ?? false) || - it.workspace?.includes(search) || + ((it.name?.includes(search) ?? false) || + (it.url?.includes(search) ?? false) || + it.uuid?.includes(search) || it.createdBy?.includes(search)) && ((showActive && isActiveMode(it.mode)) || (showArchived && isArchivingMode(it.mode)) || @@ -92,7 +93,7 @@ case SortingRule.LastVisit: return (b.lastVisit ?? 0) - (a.lastVisit ?? 0) } - return (b.workspaceUrl ?? b.workspace).localeCompare(a.workspaceUrl ?? a.workspace) + return (b.url ?? b.uuid).localeCompare(a.url ?? a.uuid) }) let backupIdx = new Map() @@ -156,7 +157,7 @@ backupable = mixedBackupSorting for (const [idx, it] of mixedBackupSorting.entries()) { - newBackupIdx.set(it.workspace, idx) + newBackupIdx.set(it.uuid, idx) } backupIdx = newBackupIdx } @@ -184,7 +185,7 @@ let showOther: boolean = true $: groupped = groupByArray(sortedWorkspaces, (it) => { - const lastUsageDays = Math.round((now - it.lastVisit) / (1000 * 3600 * 24)) + const lastUsageDays = Math.round((now - (it.lastVisit ?? 0)) / (1000 * 3600 * 24)) return Object.entries(dayRanges).find(([_k, v]) => lastUsageDays <= v)?.[0] ?? 'Other' }) @@ -202,10 +203,10 @@ $: byVersion = groupByArray( workspaces.filter((it) => { - const lastUsed = Math.round((now - it.lastVisit) / (1000 * 3600 * 24)) + const lastUsed = Math.round((now - (it.lastVisit ?? 0)) / (1000 * 3600 * 24)) return isActiveMode(it.mode) && lastUsed < 1 }), - (it) => versionToString(it.version ?? { major: 0, minor: 0, patch: 0 }) + (it) => versionToString({ major: it.versionMajor, minor: it.versionMinor, patch: it.versionPatch }) ) let superAdminMode = false @@ -334,7 +335,7 @@ message: getEmbeddedLabel(`Please confirm archive ${archivedV.length} workspaces`), action: async () => { void performWorkspaceOperation( - archivedV.map((it) => it.workspace), + archivedV.map((it) => it.uuid), 'archive' ) } @@ -354,7 +355,7 @@ message: getEmbeddedLabel(`Please confirm migrate ${archivedV.length} workspaces`), action: async () => { await performWorkspaceOperation( - activeV.map((it) => it.workspace), + activeV.map((it) => it.uuid), 'migrate-to', selectedRegionId ) @@ -365,9 +366,9 @@ {/if} {#each v.slice(0, limit) as workspace} - {@const wsName = workspace.workspaceName ?? workspace.workspace} - {@const lastUsageDays = Math.round((now - workspace.lastVisit) / (1000 * 3600 * 24))} - {@const bIdx = backupIdx.get(workspace.workspace)} + {@const wsName = workspace.name} + {@const lastUsageDays = Math.round((now - (workspace.lastVisit ?? 0)) / (1000 * 3600 * 24))} + {@const bIdx = backupIdx.get(workspace.uuid)}
@@ -375,11 +376,7 @@ {wsName}
-
@@ -400,12 +397,9 @@ {workspace.attempts} - - {#if workspace.progress !== 100 && workspace.progress !== 0} - ({workspace.progress}%) + {#if workspace.processingProgress !== 100 && workspace.processingProgress !== 0} + ({workspace.processingProgress}%) {/if} @@ -447,10 +441,10 @@ kind={'ghost'} on:click={() => { showPopup(MessageBox, { - label: getEmbeddedLabel(`Archive ${workspace.workspaceUrl}`), + label: getEmbeddedLabel(`Archive ${workspace.url}`), message: getEmbeddedLabel('Please confirm'), action: async () => { - await performWorkspaceOperation(workspace.workspace, 'archive') + await performWorkspaceOperation(workspace.uuid, 'archive') } }) }} @@ -465,10 +459,10 @@ label={getEmbeddedLabel('Unarchive')} on:click={() => { showPopup(MessageBox, { - label: getEmbeddedLabel(`Unarchive ${workspace.workspaceUrl}`), + label: getEmbeddedLabel(`Unarchive ${workspace.url}`), message: getEmbeddedLabel('Please confirm'), action: async () => { - await performWorkspaceOperation(workspace.workspace, 'unarchive') + await performWorkspaceOperation(workspace.uuid, 'unarchive') } }) }} @@ -483,10 +477,10 @@ label={getEmbeddedLabel('Migrate ' + (selectedRegionName ?? ''))} on:click={() => { showPopup(MessageBox, { - label: getEmbeddedLabel(`Migrate ${workspace.workspaceUrl}`), + label: getEmbeddedLabel(`Migrate ${workspace.url}`), message: getEmbeddedLabel('Please confirm'), action: async () => { - await performWorkspaceOperation(workspace.workspace, 'migrate-to', selectedRegionId) + await performWorkspaceOperation(workspace.uuid, 'migrate-to', selectedRegionId) } }) }} @@ -501,10 +495,10 @@ label={getEmbeddedLabel('Delete')} on:click={() => { showPopup(MessageBox, { - label: getEmbeddedLabel(`Delete ${workspace.workspaceUrl}`), + label: getEmbeddedLabel(`Delete ${workspace.url}`), message: getEmbeddedLabel('Please confirm'), action: async () => { - await performWorkspaceOperation(workspace.workspace, 'delete') + await performWorkspaceOperation(workspace.uuid, 'delete') } }) }} diff --git a/pods/green/src/index.ts b/pods/green/src/index.ts index 622c7f16ad..fe52e7c64f 100644 --- a/pods/green/src/index.ts +++ b/pods/green/src/index.ts @@ -40,6 +40,7 @@ async function toResponse (compression: string, data: any, response: http.Server .writeHead(200, { 'content-type': 'application/json', compression: 'snappy', + 'content-encoding': 'snappy', 'keep-alive': 'timeout=5' }) .end(await compress(JSON.stringify(data))) @@ -80,7 +81,7 @@ async function handleSQLFind ( const qid = ++queryId try { const lq = (json.query as string).toLowerCase() - if (lq.includes('begin') || lq.includes('commit') || lq.includes('rollback')) { + if (filterInappropriateQuries(lq)) { console.error('not allowed', json.query) response.writeHead(403).end('Not allowed') return @@ -128,3 +129,7 @@ const reqHandler = (req: http.IncomingMessage, resp: http.ServerResponse): void } http.createServer(reqHandler).listen(port) +function filterInappropriateQuries (lq: string): boolean { + const harmfulPatterns = ['begin', 'commit', 'rollback', 'drop', 'alter', 'truncate'] + return harmfulPatterns.some((pattern) => lq.includes(pattern)) +} diff --git a/server-plugins/ai-bot-resources/src/index.ts b/server-plugins/ai-bot-resources/src/index.ts index 5f3c177925..3e4c27657b 100644 --- a/server-plugins/ai-bot-resources/src/index.ts +++ b/server-plugins/ai-bot-resources/src/index.ts @@ -13,10 +13,8 @@ // limitations under the License. // -import aiBot from '@hcengineering/ai-bot' -import analyticsCollector from '@hcengineering/analytics-collector' -import chunter, { ChatMessage, ThreadMessage } from '@hcengineering/chunter' -import core, { AttachedDoc, Tx, TxCreateDoc, TxCUD, TxProcessor } from '@hcengineering/core' +import { ChatMessage } from '@hcengineering/chunter' +import { AttachedDoc, Tx, TxCreateDoc, TxCUD } from '@hcengineering/core' import { ActivityInboxNotification, MentionInboxNotification } from '@hcengineering/notification' import { TriggerControl } from '@hcengineering/server-core' @@ -133,78 +131,61 @@ import { TriggerControl } from '@hcengineering/server-core' // } // } +// eslint-disable-next-line @typescript-eslint/no-unused-vars async function onBotDirectMessageSend (control: TriggerControl, message: ChatMessage): Promise { // TODO: FIXME - throw new Error('Not implemented') // const account = control.modelDb.findAllSync(contact.class.PersonAccount, { // _id: (message.createdBy ?? message.modifiedBy) as PersonId // })[0] - // if (account === undefined) { // return // } - // const direct = (await getMessageDoc(message, control)) as DirectMessage - // if (direct === undefined) { // return // } - // const isAvailable = await isDirectAvailable(direct, control) - // if (!isAvailable) { // return // } - // let messageEvent: AIMessageEventRequest - // if (control.hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) { // messageEvent = getThreadMessageData(message as ThreadMessage, account.email) // } else { // messageEvent = getMessageData(direct, message, account.email) // } - // const transferEvent = await createTransferEvent(control, message, account, messageEvent) // const events = transferEvent !== undefined ? [messageEvent, transferEvent] : [messageEvent] - // await sendAIEvents(events, control.workspace.uuid, control.ctx) } +// eslint-disable-next-line @typescript-eslint/no-unused-vars async function onSupportWorkspaceMessage (control: TriggerControl, message: ChatMessage): Promise { // TODO: FIXME - throw new Error('Not implemented') // const supportWorkspaceId = getSupportWorkspaceId() - // if (supportWorkspaceId === undefined) { // return // } - // if (control.workspace.uuid !== supportWorkspaceId) { // return // } - // if (!control.hierarchy.isDerived(message.attachedToClass, analyticsCollector.class.OnboardingChannel)) { // return // } - // const channel = (await getMessageDoc(message, control)) as OnboardingChannel - // if (channel === undefined) { // return // } - // const { workspaceId, email } = channel // const account = control.modelDb.findAllSync(contact.class.PersonAccount, { // _id: (message.createdBy ?? message.modifiedBy) as PersonId // })[0] - // let data: AIMessageEventRequest // if (control.hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) { // data = getThreadMessageData(message as ThreadMessage, account.email) // } else { // data = getMessageData(channel, message, account.email) // } - // const transferEvent: AITransferEventRequest = { // type: AIEventType.Transfer, // createdOn: data.createdOn, @@ -219,39 +200,39 @@ async function onSupportWorkspaceMessage (control: TriggerControl, message: Chat // messageId: message._id, // parentMessageId: await getThreadParent(control, message) // } - // await sendAIEvents([transferEvent], control.workspace.uuid, control.ctx) } export async function OnMessageSend (originTxs: TxCUD[], control: TriggerControl): Promise { - const { hierarchy } = control - const txes = originTxs.filter( - (it) => - it._class === core.class.TxCreateDoc && - hierarchy.isDerived(it.objectClass, chunter.class.ChatMessage) && - !(it.modifiedBy === aiBot.account.AIBot || it.modifiedBy === core.account.System) - ) - if (txes.length === 0) { - return [] - } - for (const tx of txes) { - const isThread = hierarchy.isDerived(tx.objectClass, chunter.class.ThreadMessage) - const message = TxProcessor.createDoc2Doc(tx as TxCreateDoc) - - const docClass = isThread ? (message as ThreadMessage).objectClass : message.attachedToClass - - if (!hierarchy.isDerived(docClass, chunter.class.ChunterSpace)) { - continue - } - - if (docClass === chunter.class.DirectMessage) { - await onBotDirectMessageSend(control, message) - } - - if (docClass === analyticsCollector.class.OnboardingChannel) { - await onSupportWorkspaceMessage(control, message) - } - } + // TODO: FIXME + // const { hierarchy } = control + // const txes = originTxs.filter( + // (it) => + // it._class === core.class.TxCreateDoc && + // hierarchy.isDerived(it.objectClass, chunter.class.ChatMessage) && + // !(it.modifiedBy === aiBot.account.AIBot || it.modifiedBy === core.account.System) + // ) + // if (txes.length === 0) { + // return [] + // } + // for (const tx of txes) { + // const isThread = hierarchy.isDerived(tx.objectClass, chunter.class.ThreadMessage) + // const message = TxProcessor.createDoc2Doc(tx as TxCreateDoc) + // + // const docClass = isThread ? (message as ThreadMessage).objectClass : message.attachedToClass + // + // if (!hierarchy.isDerived(docClass, chunter.class.ChunterSpace)) { + // continue + // } + // + // if (docClass === chunter.class.DirectMessage) { + // await onBotDirectMessageSend(control, message) + // } + // + // if (docClass === analyticsCollector.class.OnboardingChannel) { + // await onSupportWorkspaceMessage(control, message) + // } + // } return [] } diff --git a/server-plugins/contact/src/utils.ts b/server-plugins/contact/src/utils.ts index 75e1295b2a..c6bc1c31eb 100644 --- a/server-plugins/contact/src/utils.ts +++ b/server-plugins/contact/src/utils.ts @@ -15,7 +15,7 @@ import { TriggerControl } from '@hcengineering/server-core' import contact, { Employee, type Person } from '@hcengineering/contact' -import { PersonId, toIdMap, parseSocialIdString, type Ref } from '@hcengineering/core' +import { parseSocialIdString, PersonId, type Ref, toIdMap } from '@hcengineering/core' export async function getTriggerCurrentPerson (control: TriggerControl): Promise { const { type, value } = parseSocialIdString(control.txFactory.account) @@ -79,9 +79,13 @@ export async function getAllSocialStringsByPersonId ( export async function getPerson (control: TriggerControl, personId: PersonId): Promise { const socialId = (await control.findAll(control.ctx, contact.class.SocialIdentity, { key: personId }))[0] - const person = (await control.findAll(control.ctx, contact.class.Person, { _id: socialId.attachedTo }))[0] - return person + if (socialId === undefined) { + control.ctx.error('Cannot find social id', { key: personId }) + return undefined + } + + return (await control.findAll(control.ctx, contact.class.Person, { _id: socialId.attachedTo }))[0] } export async function getPersons (control: TriggerControl, personIds: PersonId[]): Promise { diff --git a/server-plugins/gmail-resources/src/index.ts b/server-plugins/gmail-resources/src/index.ts index a36c80db24..5c348dd22f 100644 --- a/server-plugins/gmail-resources/src/index.ts +++ b/server-plugins/gmail-resources/src/index.ts @@ -147,16 +147,12 @@ async function notifyByEmail ( message?: ActivityMessage ): Promise { // TODO: FIXME - throw new Error('Not implemented') // const account = receiver.account - // if (account === undefined) { // return // } - // const senderPerson = sender.person // const senderName = senderPerson !== undefined ? formatName(senderPerson.name, control.branding?.lastNameFirst) : '' - // const content = await getContentByTemplate(doc, senderName, type, control, '', data, message) // if (content !== undefined) { // await sendEmailNotification(control.ctx, content.text, content.html, content.subject, account.email) @@ -173,7 +169,6 @@ const SendEmailNotifications: NotificationProviderFunc = async ( message?: ActivityMessage ): Promise => { // TODO: FIXME - throw new Error('Not implemented') // if (types.length === 0) { // return [] // } @@ -190,7 +185,7 @@ const SendEmailNotifications: NotificationProviderFunc = async ( // await notifyByEmail(control, type._id, object, sender, receiver, data, message) // } - // return [] + return [] } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type diff --git a/server-plugins/setting-resources/src/index.ts b/server-plugins/setting-resources/src/index.ts index 5c508c873b..3ff63c328e 100644 --- a/server-plugins/setting-resources/src/index.ts +++ b/server-plugins/setting-resources/src/index.ts @@ -45,7 +45,6 @@ export async function getValue (control: TriggerControl, context: Record { // TODO: FIXME // Related to integrations - throw new Error('Not implemented') // const employeeAccount = control.modelDb.findAllSync(contact.class.PersonAccount, { // _id: _id as PersonId // })[0] @@ -57,6 +56,7 @@ async function getEmployee (control: TriggerControl, _id: PersonId): Promise ): Promise { // TODO: FIXME - throw new Error('Not implemented') // const account = await control.modelDb.findOne(contact.class.PersonAccount, { // _id: control.txFactory.account as PersonId // }) @@ -125,6 +124,8 @@ export async function GetCurrentEmployeeTG ( // if (employee !== undefined) { // return await getContactChannel(control, employee, contact.channelProvider.Telegram) // } + + return undefined } export async function GetIntegrationOwnerTG ( @@ -132,7 +133,6 @@ export async function GetIntegrationOwnerTG ( context: Record ): Promise { // TODO: FIXME - throw new Error('Not implemented') // const value = context[setting.class.Integration] as Integration // if (value === undefined) return // const account = await control.modelDb.findOne(contact.class.PersonAccount, { @@ -145,6 +145,8 @@ export async function GetIntegrationOwnerTG ( // if (employee !== undefined) { // return await getContactChannel(control, employee, contact.channelProvider.Telegram) // } + + return undefined } async function getContactChannel ( @@ -261,7 +263,6 @@ const SendTelegramNotifications: NotificationProviderFunc = async ( message?: ActivityMessage ): Promise => { // TODO: FIXME - throw new Error('Not implemented') // if (types.length === 0) { // return [] // } @@ -312,7 +313,7 @@ const SendTelegramNotifications: NotificationProviderFunc = async ( // }) // } - // return [] + return [] } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type diff --git a/server-plugins/tracker-resources/src/index.ts b/server-plugins/tracker-resources/src/index.ts index adbce188a2..cf3d9de079 100644 --- a/server-plugins/tracker-resources/src/index.ts +++ b/server-plugins/tracker-resources/src/index.ts @@ -37,7 +37,14 @@ import { getSocialStrings } from '@hcengineering/server-contact' import serverCore, { TriggerControl } from '@hcengineering/server-core' import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification' import { stripTags } from '@hcengineering/text' -import tracker, { Component, Issue, IssueParentInfo, TimeSpendReport, trackerId, type Project } from '@hcengineering/tracker' +import tracker, { + Component, + Issue, + IssueParentInfo, + TimeSpendReport, + trackerId, + type Project +} from '@hcengineering/tracker' import { workbenchId } from '@hcengineering/workbench' async function updateSubIssues ( diff --git a/server/account/src/collections/mongo.ts b/server/account/src/collections/mongo.ts index 7485d026a5..a5caa01c0b 100644 --- a/server/account/src/collections/mongo.ts +++ b/server/account/src/collections/mongo.ts @@ -272,7 +272,10 @@ export class WorkspaceStatusMongoDbCollection implements DbCollection, sort?: Sort, limit?: number): Promise { - return (await this.wsCollection.find(this.toWsQuery(query), this.toWsSort(sort), limit)).map((ws) => ws.status) + return (await this.wsCollection.find(this.toWsQuery(query), this.toWsSort(sort), limit)).map((ws) => ({ + ...ws.status, + workspaceUuid: ws.uuid + })) } async findOne (query: Query): Promise { diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index e49258b5c4..84e1d2d899 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -91,6 +91,8 @@ import { // Move to config? const processingTimeoutMs = 30 * 1000 +const ADMIN_EMAILS = new Set(process.env.ADMIN_EMAILS?.split(',') ?? []) + /* =================================== */ /* ============OPERATIONS============= */ /* =================================== */ @@ -126,11 +128,12 @@ export async function login ( const isConfirmed = emailSocialId.verifiedOn != null - ctx.info('Login succeeded', { email, normalizedEmail, isConfirmed, emailSocialId }) + const isAdmin: Record = ADMIN_EMAILS.has(email.trim()) ? { admin: 'true' } : {} + ctx.info('Login succeeded', { email, normalizedEmail, isConfirmed, emailSocialId, ...isAdmin }) return { account: existingAccount.uuid, - token: isConfirmed ? generateToken(existingAccount.uuid) : undefined + token: isConfirmed ? generateToken(existingAccount.uuid, undefined, isAdmin) : undefined } } catch (err: any) { Analytics.handleError(err) @@ -835,7 +838,7 @@ export async function listWorkspaces ( ): Promise { const { extra } = decodeTokenVerbose(ctx, token) - if (!['tool', 'backup', 'admin'].includes(extra?.service)) { + if (!['tool', 'backup', 'admin'].includes(extra?.service) && extra?.admin !== 'true') { throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) } diff --git a/server/core/src/adapter.ts b/server/core/src/adapter.ts index 8554250275..9411dd8d41 100644 --- a/server/core/src/adapter.ts +++ b/server/core/src/adapter.ts @@ -16,7 +16,6 @@ import { type WorkspaceIds, type Class, - type Data, type Doc, type DocumentQuery, type Domain, @@ -86,13 +85,6 @@ export interface DbAdapter extends LowLevelStorage { tx: (ctx: MeasureContext, ...tx: Tx[]) => Promise - // Bulk update operations - update: ( - ctx: MeasureContext, - domain: Domain, - operations: Map, Partial>> - ) => Promise - // Allow to register a handler to listen for domain operations on?: (handler: DbAdapterHandler) => void } diff --git a/server/core/src/mem.ts b/server/core/src/mem.ts index 92cdaa3686..dc8cd0deb2 100644 --- a/server/core/src/mem.ts +++ b/server/core/src/mem.ts @@ -15,7 +15,6 @@ import core, { type Class, - type Data, type Doc, type DocumentQuery, type DocumentUpdate, @@ -106,12 +105,6 @@ export class DummyDbAdapter implements DbAdapter { return Promise.resolve('') } - async update( - ctx: MeasureContext, - domain: Domain, - operations: Map, Partial>> - ): Promise {} - async groupBy( ctx: MeasureContext, domain: Domain, diff --git a/server/indexer/src/indexer/indexer.ts b/server/indexer/src/indexer/indexer.ts index addd3b3237..b4bd03d701 100644 --- a/server/indexer/src/indexer/indexer.ts +++ b/server/indexer/src/indexer/indexer.ts @@ -781,7 +781,9 @@ export class FullTextIndexPipeline implements FullTextPipeline { await pushToIndex() await pushQueue.waitProcessing() - await ctx.with('update-index-state', {}, (ctx) => this.storage.update(ctx, DOMAIN_DOC_INDEX_STATE, docUpdates)) + await ctx.with('update-index-state', {}, (ctx) => + this.storage.rawUpdate(DOMAIN_DOC_INDEX_STATE, DOMAIN_DOC_INDEX_STATE, docUpdates) + ) } private createContextData (): SessionDataImpl { diff --git a/server/mongo/src/storage.ts b/server/mongo/src/storage.ts index 14860a1ded..23f103e3b0 100644 --- a/server/mongo/src/storage.ts +++ b/server/mongo/src/storage.ts @@ -1163,61 +1163,6 @@ abstract class MongoAdapterBase implements DbAdapter { }) } - update (ctx: MeasureContext, domain: Domain, operations: Map, Partial>): Promise { - return ctx.with('update', { domain }, async () => { - const coll = this.collection(domain) - - // remove old and insert new ones - const ops = Array.from(operations.entries()) - let skip = 500 - while (ops.length > 0) { - const part = ops.splice(0, skip) - try { - await ctx.with( - 'bulk-update', - {}, - () => { - return coll.bulkWrite( - part.map((it) => { - const { $unset, ...set } = it[1] as any - if ($unset !== undefined) { - for (const k of Object.keys(set)) { - if ($unset[k] === '') { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete $unset[k] - } - } - } - return { - updateOne: { - filter: { _id: it[0] }, - update: { - $set: { ...set, '%hash%': this.curHash() }, - ...($unset !== undefined ? { $unset } : {}) - } - } - } - }), - { - ordered: false - } - ) - }, - { - updates: part.length - } - ) - } catch (err: any) { - ctx.error('failed on bulk write', { error: err, skip }) - if (skip !== 1) { - ops.push(...part) - skip = 1 // Let's update one by one, to loose only one failed variant. - } - } - } - }) - } - clean (ctx: MeasureContext, domain: Domain, docs: Ref[]): Promise { return ctx.with('clean', {}, async () => { if (docs.length > 0) { diff --git a/server/postgres/src/__tests__/conversion.spec.ts b/server/postgres/src/__tests__/conversion.spec.ts index 62e2f1b07a..f1162791a4 100644 --- a/server/postgres/src/__tests__/conversion.spec.ts +++ b/server/postgres/src/__tests__/conversion.spec.ts @@ -1,4 +1,19 @@ +import core, { + Hierarchy, + MeasureMetricsContext, + ModelDb, + TxFactory, + type DocumentUpdate, + type PersonId, + type Ref, + type Space, + type Tx, + type WorkspaceUuid +} from '@hcengineering/core' +import { PostgresAdapter } from '../storage' import { convertArrayParams, decodeArray } from '../utils' +import { genMinModel, test, type ComplexClass } from './minmodel' +import { createDummyClient, type TypedQuery } from './utils' describe('array conversion', () => { it('should handle undefined parameters', () => { @@ -55,3 +70,93 @@ describe('array decoding', () => { expect(decodeArray('{"first \\"quote\\"","second \\"quote\\""}')).toEqual(['first "quote"', 'second "quote"']) }) }) + +const factory = new TxFactory('email:test' as PersonId) +function upd (id: string, partial: DocumentUpdate): Tx { + return factory.createTxUpdateDoc( + test.class.ComplexClass, + core.space.Workspace, + id as Ref, + partial + ) +} + +describe('query to sql conversion tests', () => { + it('check dummy db client', async () => { + const queries: TypedQuery[] = [] + const c = createDummyClient(queries) + + await c.execute('select now()') + expect(queries[0].query).toEqual('select now()') + }) + it('check simple update', async () => { + const { adapter, ctx, queries } = createTestContext() + + await adapter.tx( + ctx, + upd('obj1', { + stringField: 'test' + }) + ) + expect(queries[0].query).toEqual( + 'UPDATE pg_testing SET "modifiedBy" = update_data."_modifiedBy", "modifiedOn" = update_data."_modifiedOn", "%hash%" = update_data."_%hash%", data = COALESCE(data || update_data._data)\n FROM (values ($2::text, $3::text,$4::bigint,$5::text,$6::jsonb)) AS update_data(__id, "_modifiedBy","_modifiedOn","_%hash%","_data")\n WHERE "workspaceId" = $1::uuid AND "_id" = update_data.__id' + ) + }) + it('check space update', async () => { + const { adapter, ctx, queries } = createTestContext() + + await adapter.tx( + ctx, + upd('obj1', { + space: 'new-space' as Ref + }) + ) + expect(queries[0].query).toEqual( + 'UPDATE pg_testing SET "modifiedBy" = update_data."_modifiedBy", "modifiedOn" = update_data."_modifiedOn", "%hash%" = update_data."_%hash%", "space" = update_data."_space"\n FROM (values ($2::text, $3::text,$4::bigint,$5::text,$6::text)) AS update_data(__id, "_modifiedBy","_modifiedOn","_%hash%","_space")\n WHERE "workspaceId" = $1::uuid AND "_id" = update_data.__id' + ) + }) + it('check few documents update', async () => { + const { adapter, ctx, queries } = createTestContext() + + await adapter.tx( + ctx, + upd('obj1', { + stringField: 'test' + }), + upd('obj2', { + stringField: 'test2' + }), + upd('obj3', { + stringField: 'test' + }) + ) + expect(queries[0].query).toEqual( + 'UPDATE pg_testing SET "modifiedBy" = update_data."_modifiedBy", "modifiedOn" = update_data."_modifiedOn", "%hash%" = update_data."_%hash%", data = COALESCE(data || update_data._data)\n FROM (values ($2::text, $3::text,$4::bigint,$5::text,$6::jsonb),($7::text, $8::text,$9::bigint,$10::text,$11::jsonb),($12::text, $13::text,$14::bigint,$15::text,$16::jsonb)) AS update_data(__id, "_modifiedBy","_modifiedOn","_%hash%","_data")\n WHERE "workspaceId" = $1::uuid AND "_id" = update_data.__id' + ) + }) +}) +function createTestContext (): { adapter: PostgresAdapter, ctx: MeasureMetricsContext, queries: TypedQuery[] } { + const ctx = new MeasureMetricsContext('test', {}) + const queries: TypedQuery[] = [] + const c = createDummyClient(queries) + + const minModel = genMinModel() + const hierarchy = new Hierarchy() + for (const tx of minModel) { + hierarchy.tx(tx) + } + const modelDb = new ModelDb(hierarchy) + modelDb.addTxes(ctx, minModel, true) + const adapter = new PostgresAdapter( + c, + { + url: () => 'test', + close: () => {} + }, + 'workspace' as WorkspaceUuid, + hierarchy, + modelDb, + 'test' + ) + return { adapter, ctx, queries } +} diff --git a/server/postgres/src/__tests__/minmodel.ts b/server/postgres/src/__tests__/minmodel.ts index 80910fce69..3eb179e622 100644 --- a/server/postgres/src/__tests__/minmodel.ts +++ b/server/postgres/src/__tests__/minmodel.ts @@ -14,19 +14,20 @@ // import core, { - type PersonId, type Arr, type AttachedDoc, type Class, ClassifierKind, type Data, type Doc, + type Domain, DOMAIN_DOC_INDEX_STATE, DOMAIN_MODEL, DOMAIN_RELATION, DOMAIN_TX, type Mixin, type Obj, + type PersonId, type Ref, type TxCreateDoc, type TxCUD, @@ -72,15 +73,33 @@ export interface AttachedComment extends AttachedDoc { message: string } +export interface ComplexClass extends Doc { + stringField: string + numberField: number + booleanField: boolean + arrayField: string[] + numberArrayField: number[] +} + +export interface ComplexMixin extends Mixin { + stringField: string + numberField: number + booleanField: boolean + arrayField: string[] + numberArrayField: number[] +} + /** * @public */ export const test = plugin('test' as Plugin, { mixin: { - TestMixin: '' as Ref> + TestMixin: '' as Ref>, + ComplexMixin: '' as Ref> }, class: { - TestComment: '' as Ref> + TestComment: '' as Ref>, + ComplexClass: '' as Ref> } }) @@ -197,6 +216,23 @@ export function genMinModel (): TxCUD[] { kind: ClassifierKind.CLASS }) ) + txes.push( + createClass(test.class.ComplexClass, { + label: 'ComplexClass' as IntlString, + extends: core.class.Doc, + kind: ClassifierKind.CLASS, + domain: 'pg-testing' as Domain + }) + ) + + txes.push( + createClass(test.mixin.ComplexMixin, { + label: 'ComplexMixin' as IntlString, + extends: test.class.ComplexClass, + kind: ClassifierKind.MIXIN, + domain: 'pg-testing' as Domain + }) + ) const u1 = 'User1' as PersonId const u2 = 'User2' as PersonId diff --git a/server/postgres/src/__tests__/utils.ts b/server/postgres/src/__tests__/utils.ts new file mode 100644 index 0000000000..96b2da94ee --- /dev/null +++ b/server/postgres/src/__tests__/utils.ts @@ -0,0 +1,18 @@ +import type { DBClient } from '../client' + +export interface TypedQuery { + query: string + params?: any[] +} +export function createDummyClient (queries: TypedQuery[]): DBClient { + const client: DBClient = { + execute: async (query, params) => { + queries.push({ query, params }) + return Object.assign([], { count: 0 }) + }, + raw: () => jest.fn() as any, + reserve: async () => client, + release: jest.fn() + } + return client +} diff --git a/server/postgres/src/client.ts b/server/postgres/src/client.ts index be9e905f5e..9f54824da9 100644 --- a/server/postgres/src/client.ts +++ b/server/postgres/src/client.ts @@ -83,8 +83,7 @@ class GreenClient implements DBClient { release (): void {} async reserve (): Promise { - // We do reserve of connection, if we need it. - return createGreenDBClient(this.url, this.token, await this.connection.reserve(), this.decoder) + return createDBClient(await this.connection.reserve()) } raw (): postgres.Sql { diff --git a/server/postgres/src/storage.ts b/server/postgres/src/storage.ts index 30a209f3bf..009b314755 100644 --- a/server/postgres/src/storage.ts +++ b/server/postgres/src/storage.ts @@ -1684,18 +1684,6 @@ abstract class PostgresAdapterBase implements DbAdapter { }) } - async update (ctx: MeasureContext, domain: Domain, operations: Map, Partial>): Promise { - const ids = [...operations.entries()] - const groups = groupByArray(ids, (it) => JSON.stringify(it[1])) - for (const [, values] of groups.entries()) { - const ids = values.map((it) => it[0]) - while (ids.length > 0) { - const part = ids.splice(0, 200) - await this.rawUpdate(domain, { _id: { $in: part } }, values[0][1]) - } - } - } - @withContext('insert') async insert (ctx: MeasureContext, domain: string, docs: Doc[]): Promise { await this.upload(ctx, domain as Domain, docs, false) @@ -1714,7 +1702,7 @@ interface OperationBulk { const initRateLimit = new RateLimiter(1) -class PostgresAdapter extends PostgresAdapterBase { +export class PostgresAdapter extends PostgresAdapterBase { async init ( ctx: MeasureContext, contextVars: Record, @@ -1819,7 +1807,7 @@ class PostgresAdapter extends PostgresAdapterBase { result.push(res) } } - // TODO: Optimize updates + if (ops.updates.length > 0) { const res = await this.txUpdateDoc(ctx, domain, ops.updates, domainFields) for (const r of res) { diff --git a/server/server/src/utils.ts b/server/server/src/utils.ts index 06e40811e9..389fecc7f6 100644 --- a/server/server/src/utils.ts +++ b/server/server/src/utils.ts @@ -42,7 +42,7 @@ export function doSessionOp ( ): void { if (data.session instanceof Promise) { // We need to copy since we will out of protected buffer area - const msgCopy = Buffer.copyBytesFrom(msg) + const msgCopy = Buffer.copyBytesFrom(new Uint8Array(msg)) void data.session .then((_session) => { data.session = _session diff --git a/server/tool/src/index.ts b/server/tool/src/index.ts index cb0fecc8d7..cd30e8d2e6 100644 --- a/server/tool/src/index.ts +++ b/server/tool/src/index.ts @@ -111,7 +111,7 @@ export async function initModel ( } try { - logger.log('creating database...', workspaceId) + logger.log('creating database...', { workspaceId }) const firstTx: Tx = { _class: core.class.Tx, _id: 'first-tx' as Ref, diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml index 73162d5129..85ff38005f 100644 --- a/tests/docker-compose.yaml +++ b/tests/docker-compose.yaml @@ -12,7 +12,6 @@ services: image: cockroachdb/cockroach:latest-v24.2 ports: - '26258:26257' - - '8089:8080' command: start-single-node --insecure restart: unless-stopped minio: diff --git a/workers/transactor/src/transactor.ts b/workers/transactor/src/transactor.ts index 0d478d3eef..3d8116d973 100644 --- a/workers/transactor/src/transactor.ts +++ b/workers/transactor/src/transactor.ts @@ -219,13 +219,14 @@ export class Transactor extends DurableObject { const st = Date.now() const r = this.sessionManager.handleRequest(this.measureCtx, s.session, cs, request, this.workspace) void r.finally(() => { + const time = Date.now() - st console.log({ - message: 'handle-request', + message: 'handle-request: ' + time, method: request.method, params: request.params, workspace: s.workspaceId, user: s.session.getUser(), - time: Date.now() - st + time }) }) this.ctx.waitUntil(r)