From 7dec3cf7fb801126187386d34f1456322a0ee079 Mon Sep 17 00:00:00 2001 From: Alexey Zinoviev Date: Fri, 14 Feb 2025 06:00:23 +0400 Subject: [PATCH] uberf-9383: fix ws init and import (#8005) Signed-off-by: Alexey Zinoviev --- .vscode/launch.json | 17 ++-- dev/docker-compose.yaml | 8 +- dev/tool/src/db.ts | 2 +- dev/tool/src/index.ts | 3 +- packages/account-client/src/client.ts | 13 ++- packages/core/src/classes.ts | 4 + packages/importer/src/huly/unified.ts | 96 +++++++++---------- pods/workspace/.gitignore | 1 + server/account/src/operations.ts | 38 +++++++- server/account/src/types.ts | 5 +- server/account/src/utils.ts | 11 ++- server/tool/src/index.ts | 6 +- server/tool/src/initializer.ts | 32 ++++++- server/workspace-service/src/ws-operations.ts | 29 +++++- 14 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 pods/workspace/.gitignore diff --git a/.vscode/launch.json b/.vscode/launch.json index 51fee10554..d3ee324774 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -138,11 +138,10 @@ "env": { "MONGO_URL": "mongodb://localhost:27017", "DB_URL": "mongodb://localhost:27017", - // "DB_URL": "postgresql://postgres:example@localhost:5432", // "DB_URL": "postgresql://root@host.docker.internal:26257/defaultdb?sslmode=disable", "SERVER_SECRET": "secret", - "REGION_INFO":"|Mongo;pg|Postgres;cockroach|CockroachDB", - "TRANSACTOR_URL": "ws://host.docker.internal:3333,ws://host.docker.internal:3331;;pg,ws://host.docker.internal:3332;;cockroach", + "REGION_INFO":"|Mongo;cockroach|CockroachDB", + "TRANSACTOR_URL": "ws://host.docker.internal:3333,ws://host.docker.internal:3332;;cockroach", "ACCOUNTS_URL": "http://localhost:3000", "ACCOUNT_PORT": "3000", "FRONT_URL": "http://localhost:8080", @@ -154,8 +153,6 @@ "MINIO_SECRET_KEY": "minioadmin", "MINIO_ENDPOINT": "localhost" // "DISABLE_SIGNUP": "true", - // "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml", - // "INIT_WORKSPACE": "onboarding", }, "runtimeVersion": "20", "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], @@ -187,8 +184,6 @@ "MINIO_SECRET_KEY": "minioadmin", "MINIO_ENDPOINT": "localhost" // "DISABLE_SIGNUP": "true", - // "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml", - // "INIT_WORKSPACE": "onboarding", }, "runtimeVersion": "20", "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], @@ -236,8 +231,8 @@ "WS_OPERATION": "all+backup", "BACKUP_STORAGE": "minio|minio?accessKey=minioadmin&secretKey=minioadmin", "BACKUP_BUCKET": "dev-backups", - // "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml", - // "INIT_WORKSPACE": "onboarding", + // "INIT_REPO_DIR": "${workspaceRoot}/pods/workspace/init", + // "INIT_WORKSPACE": "staging-dev" }, "runtimeVersion": "20", "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], @@ -269,8 +264,8 @@ "WS_OPERATION": "all+backup", "BACKUP_STORAGE": "minio|minio?accessKey=minioadmin&secretKey=minioadmin", "BACKUP_BUCKET": "dev-backups", - // "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml", - // "INIT_WORKSPACE": "onboarding", + // "INIT_REPO_DIR": "${workspaceRoot}/pods/workspace/init", + // "INIT_WORKSPACE": "staging-dev" }, "runtimeVersion": "20", "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index 06592146b5..bb7e47299e 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -80,7 +80,7 @@ services: # Pass only one region to disallow selection for new workspaces.Ø - REGION_INFO=|Mongo;cockroach|CockroachDB # - REGION_INFO=cockroach|CockroachDB - - TRANSACTOR_URL=ws://host.docker.internal:3333,ws://host.docker.internal:3331;;pg,ws://host.docker.internal:3332;;cockroach, + - TRANSACTOR_URL=ws://host.docker.internal:3333,ws://host.docker.internal:3332;;cockroach, - SES_URL= - STORAGE_CONFIG=${STORAGE_CONFIG} - FRONT_URL=http://host.docker.internal:8087 @@ -91,8 +91,6 @@ services: - ACCOUNTS_URL=http://host.docker.internal:3000 - BRANDING_PATH=/var/cfg/branding.json # - DISABLE_SIGNUP=true - # - INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml - # - INIT_WORKSPACE=onboarding restart: unless-stopped stats: image: hardcoreeng/stats @@ -126,9 +124,9 @@ services: - ACCOUNTS_URL=http://host.docker.internal:3000 - BRANDING_PATH=/var/cfg/branding.json # - PARALLEL=2 - - INIT_WORKSPACE=test - BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG} - BACKUP_BUCKET=${BACKUP_BUCKET_NAME} + # - INIT_WORKSPACE=staging-dev restart: unless-stopped workspace_cockroach: image: hardcoreeng/workspace @@ -153,9 +151,9 @@ services: - ACCOUNTS_URL=http://host.docker.internal:3000 - BRANDING_PATH=/var/cfg/branding.json # - PARALLEL=2 - # - INIT_WORKSPACE=onboarding - BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG} - BACKUP_BUCKET=${BACKUP_BUCKET_NAME} + # - INIT_WORKSPACE=staging-dev restart: unless-stopped collaborator: image: hardcoreeng/collaborator diff --git a/dev/tool/src/db.ts b/dev/tool/src/db.ts index 206ca5972e..5f9b9dfcfc 100644 --- a/dev/tool/src/db.ts +++ b/dev/tool/src/db.ts @@ -334,7 +334,7 @@ export async function moveAccountDbFromMongoToPG ( } if (workspacesCount % 100 === 0) { - ctx.info(`Migrated ${workspacesCount} invites...`) + ctx.info(`Migrated ${workspacesCount} workspaces...`) } } ctx.info(`Migrated ${workspacesCount} workspaces with ${membersCount} member assignments`) diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 931f66e3b5..8dc317582f 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -46,6 +46,7 @@ import { AccountRole, MeasureMetricsContext, metricsToString, + type PersonId, type Data, type Tx, type Version, @@ -304,7 +305,7 @@ export function devTool ( const measureCtx = new MeasureMetricsContext('create-workspace', {}) const brandingObj = cmd.branding !== undefined || cmd.init !== undefined ? { key: cmd.branding, initWorkspace: cmd.init } : null - const socialId = await db.socialId.findOne({ key: socialString }) + const socialId = await db.socialId.findOne({ key: socialString as PersonId }) if (socialId == null) { throw new Error(`Social id ${socialString} not found`) } diff --git a/packages/account-client/src/client.ts b/packages/account-client/src/client.ts index 563cf03064..7595b7c74a 100644 --- a/packages/account-client/src/client.ts +++ b/packages/account-client/src/client.ts @@ -17,7 +17,8 @@ import { BackupStatus, Data, type Person, - PersonUuid, + type PersonUuid, + type PersonInfo, SocialId, Version, type WorkspaceInfoWithStatus, @@ -73,6 +74,7 @@ export interface AccountClient { signUp: (email: string, password: string, first: string, last: string) => Promise login: (email: string, password: string) => Promise getPerson: () => Promise + getPersonInfo: (account: PersonUuid) => Promise getSocialIds: () => Promise getWorkspaceMembers: () => Promise updateWorkspaceRole: (account: string, role: AccountRole) => Promise @@ -399,6 +401,15 @@ class AccountClientImpl implements AccountClient { return await this.rpc(request) } + async getPersonInfo (account: PersonUuid): Promise { + const request = { + method: 'getPersonInfo' as const, + params: [account] + } + + return await this.rpc(request) + } + async getSocialIds (): Promise { const request = { method: 'getSocialIds' as const, diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index 6db32427f8..08ad008ad6 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -538,6 +538,10 @@ export interface Person { city?: string } +export interface PersonInfo extends BasePerson { + socialIds: PersonId[] +} + /** * @public */ diff --git a/packages/importer/src/huly/unified.ts b/packages/importer/src/huly/unified.ts index a176073dde..6091dac6ac 100644 --- a/packages/importer/src/huly/unified.ts +++ b/packages/importer/src/huly/unified.ts @@ -16,11 +16,13 @@ import { type Attachment } from '@hcengineering/attachment' import contact, { Employee, type Person } from '@hcengineering/contact' import { + buildSocialIdString, type Class, type Doc, generateId, PersonId, type Ref, + SocialIdType, type Space, type TxOperations } from '@hcengineering/core' @@ -330,24 +332,26 @@ interface AttachmentMetadata { } export class UnifiedFormatImporter { + private readonly importerEmailPlaceholder = 'newuser@huly.io' + private readonly importerNamePlaceholder = 'New User' private readonly pathById = new Map, string>() private readonly refMetaByPath = new Map() private readonly fileMetaByPath = new Map() private readonly ctrlDocTemplateIdByPath = new Map>() private personsByName = new Map>() - // private accountsByEmail = new Map>() - // private employeesByName = new Map>() + private employeesByName = new Map>() constructor ( private readonly client: TxOperations, private readonly fileUploader: FileUploader, - private readonly logger: Logger + private readonly logger: Logger, + private readonly importerSocialId?: PersonId, + private readonly importerPerson?: Ref ) {} private async initCaches (): Promise { await this.cachePersonsByNames() - await this.cacheAccountsByEmails() await this.cacheEmployeesByName() } @@ -588,6 +592,10 @@ export class UnifiedFormatImporter { if (name === undefined) { return undefined } + + if (name === this.importerNamePlaceholder && this.importerPerson != null) { + return this.importerPerson + } const person = this.personsByName.get(name) if (person === undefined) { throw new Error(`Person not found: ${name}`) @@ -595,24 +603,20 @@ export class UnifiedFormatImporter { return person } - private findAccountByEmail (email: string): PersonId { - // TODO: FIXME - throw new Error('Not implemented') - // const account = this.accountsByEmail.get(email) - // if (account === undefined) { - // throw new Error(`Account not found: ${email}`) - // } - // return account + private getSocialIdByEmail (email: string): PersonId { + if (email === this.importerEmailPlaceholder && this.importerSocialId != null) { + return this.importerSocialId + } + + return buildSocialIdString({ type: SocialIdType.EMAIL, value: email }) } private findEmployeeByName (name: string): Ref { - // TODO: FIXME - throw new Error('Not implemented') - // const employee = this.employeesByName.get(name) - // if (employee === undefined) { - // throw new Error(`Employee not found: ${name}`) - // } - // return employee + const employee = this.employeesByName.get(name) + if (employee === undefined) { + throw new Error(`Employee not found: ${name}`) + } + return employee } private async processDocumentsRecursively ( @@ -752,7 +756,7 @@ export class UnifiedFormatImporter { } return { text: comment.text, - author: this.findAccountByEmail(comment.author), + author: this.getSocialIdByEmail(comment.author), attachments } }) @@ -789,9 +793,9 @@ export class UnifiedFormatImporter { defaultIssueStatus: projectHeader.defaultIssueStatus !== undefined ? { name: projectHeader.defaultIssueStatus } : undefined, owners: - projectHeader.owners !== undefined ? projectHeader.owners.map((email) => this.findAccountByEmail(email)) : [], + projectHeader.owners !== undefined ? projectHeader.owners.map((email) => this.getSocialIdByEmail(email)) : [], members: - projectHeader.members !== undefined ? projectHeader.members.map((email) => this.findAccountByEmail(email)) : [], + projectHeader.members !== undefined ? projectHeader.members.map((email) => this.getSocialIdByEmail(email)) : [], docs: [] } } @@ -805,9 +809,9 @@ export class UnifiedFormatImporter { archived: spaceHeader.archived ?? false, description: spaceHeader.description, emoji: spaceHeader.emoji, - owners: spaceHeader.owners !== undefined ? spaceHeader.owners.map((email) => this.findAccountByEmail(email)) : [], + owners: spaceHeader.owners !== undefined ? spaceHeader.owners.map((email) => this.getSocialIdByEmail(email)) : [], members: - spaceHeader.members !== undefined ? spaceHeader.members.map((email) => this.findAccountByEmail(email)) : [], + spaceHeader.members !== undefined ? spaceHeader.members.map((email) => this.getSocialIdByEmail(email)) : [], docs: [] } } @@ -819,11 +823,11 @@ export class UnifiedFormatImporter { private: spaceHeader.private ?? false, archived: spaceHeader.archived ?? false, description: spaceHeader.description, - owners: spaceHeader.owners?.map((email) => this.findAccountByEmail(email)) ?? [], - members: spaceHeader.members?.map((email) => this.findAccountByEmail(email)) ?? [], - qualified: spaceHeader.qualified !== undefined ? this.findAccountByEmail(spaceHeader.qualified) : undefined, - manager: spaceHeader.manager !== undefined ? this.findAccountByEmail(spaceHeader.manager) : undefined, - qara: spaceHeader.qara !== undefined ? this.findAccountByEmail(spaceHeader.qara) : undefined, + owners: spaceHeader.owners?.map((email) => this.getSocialIdByEmail(email)) ?? [], + members: spaceHeader.members?.map((email) => this.getSocialIdByEmail(email)) ?? [], + qualified: spaceHeader.qualified !== undefined ? this.getSocialIdByEmail(spaceHeader.qualified) : undefined, + manager: spaceHeader.manager !== undefined ? this.getSocialIdByEmail(spaceHeader.manager) : undefined, + qara: spaceHeader.qara !== undefined ? this.getSocialIdByEmail(spaceHeader.qara) : undefined, docs: [] } } @@ -950,30 +954,18 @@ export class UnifiedFormatImporter { }, new Map()) } - private async cacheAccountsByEmails (): Promise { - // TODO: FIXME - throw new Error('Not implemented') - // const accounts = await this.client.findAll(contact.class.PersonAccount, {}) - // this.accountsByEmail = accounts.reduce((map, account) => { - // map.set(account.email, account._id) - // return map - // }, new Map()) - } - private async cacheEmployeesByName (): Promise { - // TODO: FIXME - throw new Error('Not implemented') - // this.employeesByName = (await this.client.findAll(contact.mixin.Employee, {})) - // .map((employee) => { - // return { - // _id: employee._id, - // name: employee.name.split(',').reverse().join(' ') - // } - // }) - // .reduce((refByName, employee) => { - // refByName.set(employee.name, employee._id) - // return refByName - // }, new Map()) + this.employeesByName = (await this.client.findAll(contact.mixin.Employee, {})) + .map((employee) => { + return { + _id: employee._id, + name: employee.name.split(',').reverse().join(' ') + } + }) + .reduce((refByName, employee) => { + refByName.set(employee.name, employee._id) + return refByName + }, new Map()) } private async collectFileMetadata (folderPath: string): Promise { diff --git a/pods/workspace/.gitignore b/pods/workspace/.gitignore new file mode 100644 index 0000000000..cdb8d0e600 --- /dev/null +++ b/pods/workspace/.gitignore @@ -0,0 +1 @@ +init \ No newline at end of file diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index cfc2bc9a07..ecdbd36e8f 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -28,9 +28,11 @@ import { type Branding, type Person, type PersonUuid, + type PersonInfo, type WorkspaceMemberInfo, type WorkspaceMode, - type WorkspaceUuid + type WorkspaceUuid, + type PersonId } from '@hcengineering/core' import platform, { getMetadata, @@ -86,7 +88,8 @@ import { setPassword, signUpByEmail, verifyPassword, - wrap + wrap, + verifyAllowedServices } from './utils' // Move to config? @@ -1052,6 +1055,31 @@ export async function getPerson ( return person } +export async function getPersonInfo ( + ctx: MeasureContext, + db: AccountDB, + branding: Branding | null, + token: string, + account: PersonUuid +): Promise { + const { extra } = decodeTokenVerbose(ctx, token) + verifyAllowedServices(['workspace', 'tool'], extra) + + const person = await db.person.findOne({ uuid: account }) + + if (person == null) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.PersonNotFound, { person: account })) + } + + const verifiedSocialIds = await db.socialId.find({ personUuid: account, verifiedOn: { $gt: 0 } }) + + return { + personUuid: account, + name: `${person?.firstName} ${person?.lastName}`, // Should we control the order by config? + socialIds: verifiedSocialIds.map((it) => it.key) + } +} + export async function findPerson ( ctx: MeasureContext, db: AccountDB, @@ -1061,7 +1089,7 @@ export async function findPerson ( ): Promise { decodeTokenVerbose(ctx, token) - const socialId = await db.socialId.findOne({ key: socialString }) + const socialId = await db.socialId.findOne({ key: socialString as PersonId }) if (socialId == null) { return @@ -1105,7 +1133,7 @@ export async function updateWorkspaceRoleBySocialId ( throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) } - const socialId = await getSocialIdByKey(db, socialKey.toLowerCase()) + const socialId = await getSocialIdByKey(db, socialKey.toLowerCase() as PersonId) if (socialId == null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {})) } @@ -1452,6 +1480,7 @@ export type AccountMethods = | 'updateBackupInfo' | 'assignWorkspace' | 'getPerson' + | 'getPersonInfo' | 'getWorkspaceMembers' | 'updateWorkspaceRole' | 'findPerson' @@ -1493,6 +1522,7 @@ export function getMethods (hasSignUp: boolean = true): Partial { +export async function getSocialIdByKey (db: AccountDB, socialKey: PersonId): Promise { return await db.socialId.findOne({ key: socialKey }) } @@ -1106,3 +1107,9 @@ export async function getWorkspaces ( status: statusesMap[it.uuid] })) } + +export function verifyAllowedServices (services: string[], extra: any): void { + if (!services.includes(extra?.service) && extra?.admin !== 'true') { + throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) + } +} diff --git a/server/tool/src/index.ts b/server/tool/src/index.ts index 186dd2900f..04b0671fab 100644 --- a/server/tool/src/index.ts +++ b/server/tool/src/index.ts @@ -34,7 +34,8 @@ import core, { type Client, type Ref, type WithLookup, - type WorkspaceDataId + type WorkspaceDataId, + type PersonInfo } from '@hcengineering/core' import { consoleModelLogger, MigrateOperation, ModelLogger, tryMigrate } from '@hcengineering/model' import { DomainIndexHelperImpl, Pipeline, StorageAdapter, type DbAdapter } from '@hcengineering/server-core' @@ -187,6 +188,7 @@ export async function initializeWorkspace ( ctx: MeasureContext, branding: Branding | null, wsIds: WorkspaceIds, + personInfo: PersonInfo, storageAdapter: StorageAdapter, client: TxOperations, logger: ModelLogger = consoleModelLogger, @@ -218,7 +220,7 @@ export async function initializeWorkspace ( return } - const initializer = new WorkspaceInitializer(ctx, storageAdapter, wsIds, client, initRepoDir) + const initializer = new WorkspaceInitializer(ctx, storageAdapter, wsIds, client, initRepoDir, personInfo) await initializer.processScript(script, logger, progress) } catch (err: any) { ctx.error('Failed to initialize workspace', { error: err }) diff --git a/server/tool/src/initializer.ts b/server/tool/src/initializer.ts index a9e9885b80..474d02b757 100644 --- a/server/tool/src/initializer.ts +++ b/server/tool/src/initializer.ts @@ -8,7 +8,11 @@ import core, { makeCollabId, MeasureContext, Mixin, + parseSocialIdString, + type PersonId, + type PersonInfo, Ref, + SocialIdType, Space, TxOperations, type WorkspaceDataId, @@ -19,6 +23,7 @@ import { makeRank } from '@hcengineering/rank' import { StorageFileUploader, UnifiedFormatImporter } from '@hcengineering/importer' import type { StorageAdapter } from '@hcengineering/server-core' import { jsonToMarkup, parseMessageMarkdown } from '@hcengineering/text' +import { pickPrimarySocialId } from '@hcengineering/contact' import { v4 as uuid } from 'uuid' import path from 'path' @@ -96,21 +101,38 @@ export class WorkspaceInitializer { private readonly imageUrl = 'image://' private readonly nextRank = '#nextRank' private readonly now = '#now' + private readonly creatorPersonVar = 'creatorPerson' + private readonly socialKey: PersonId + private readonly socialType: SocialIdType + private readonly socialValue: string constructor ( private readonly ctx: MeasureContext, private readonly storageAdapter: StorageAdapter, private readonly wsIds: WorkspaceIds, private readonly client: TxOperations, - private readonly initRepoDir: string - ) {} + private readonly initRepoDir: string, + private readonly creator: PersonInfo + ) { + this.socialKey = pickPrimarySocialId(creator.socialIds) + const socialKeyObj = parseSocialIdString(this.socialKey) + this.socialType = socialKeyObj.type + this.socialValue = socialKeyObj.value + } async processScript ( script: InitScript, logger: ModelLogger, progress: (value: number) => Promise ): Promise { - const vars: Record = {} + const vars: Record = { + '${creatorName@global}': this.creator.name, // eslint-disable-line no-template-curly-in-string + '${creatorUuid@global}': this.creator.personUuid, // eslint-disable-line no-template-curly-in-string + '${creatorSocialKey@global}': this.socialKey, // eslint-disable-line no-template-curly-in-string + '${creatorSocialType@global}': this.socialType, // eslint-disable-line no-template-curly-in-string + '${creatorSocialValue@global}': this.socialValue // eslint-disable-line no-template-curly-in-string + } + const defaults = new Map>, Props>() for (let index = 0; index < script.steps.length; index++) { try { @@ -169,7 +191,9 @@ export class WorkspaceInitializer { try { const uploader = new StorageFileUploader(this.ctx, this.storageAdapter, this.wsIds) const initPath = path.resolve(this.initRepoDir, step.path) - const importer = new UnifiedFormatImporter(this.client, uploader, logger) + // eslint-disable-next-line no-template-curly-in-string + const initPerson = vars[`\${${this.creatorPersonVar}}`] + const importer = new UnifiedFormatImporter(this.client, uploader, logger, this.socialKey, initPerson) await importer.importFolder(initPath) } catch (error) { logger.error('Import failed', error) diff --git a/server/workspace-service/src/ws-operations.ts b/server/workspace-service/src/ws-operations.ts index a868ba4e73..222bf5097c 100644 --- a/server/workspace-service/src/ws-operations.ts +++ b/server/workspace-service/src/ws-operations.ts @@ -101,10 +101,31 @@ export async function createWorkspace ( }) ctx.info('Starting init script if any') - await initializeWorkspace(ctx, branding, wsIds, storageAdapter, client, ctxModellogger, async (value) => { - ctx.info('Init script progress', { value }) - await handleWsEvent?.('progress', version, 20 + Math.round((Math.min(value, 100) / 100) * 60)) - }) + const creatorUuid = workspaceInfo.createdBy + + if (creatorUuid != null) { + const personInfo = await accountClient.getPersonInfo(creatorUuid) + + if (personInfo?.socialIds.length > 0) { + await initializeWorkspace( + ctx, + branding, + wsIds, + personInfo, + storageAdapter, + client, + ctxModellogger, + async (value) => { + ctx.info('Init script progress', { value }) + await handleWsEvent?.('progress', version, 20 + Math.round((Math.min(value, 100) / 100) * 60)) + } + ) + } else { + ctx.warn('No person info or verified social ids found for workspace creator. Skipping init script.') + } + } else { + ctx.warn('No workspace creator found. Skipping init script.') + } await upgradeWorkspaceWith( ctx,