mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-28 02:47:31 +00:00
uberf-9383: fix ws init and import (#8005)
Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
parent
07e9c88287
commit
7dec3cf7fb
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
@ -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"],
|
||||
|
@ -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
|
||||
|
@ -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`)
|
||||
|
@ -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`)
|
||||
}
|
||||
|
@ -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<LoginInfo>
|
||||
login: (email: string, password: string) => Promise<LoginInfo>
|
||||
getPerson: () => Promise<Person>
|
||||
getPersonInfo: (account: PersonUuid) => Promise<PersonInfo>
|
||||
getSocialIds: () => Promise<SocialId[]>
|
||||
getWorkspaceMembers: () => Promise<WorkspaceMemberInfo[]>
|
||||
updateWorkspaceRole: (account: string, role: AccountRole) => Promise<void>
|
||||
@ -399,6 +401,15 @@ class AccountClientImpl implements AccountClient {
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async getPersonInfo (account: PersonUuid): Promise<PersonInfo> {
|
||||
const request = {
|
||||
method: 'getPersonInfo' as const,
|
||||
params: [account]
|
||||
}
|
||||
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async getSocialIds (): Promise<SocialId[]> {
|
||||
const request = {
|
||||
method: 'getSocialIds' as const,
|
||||
|
@ -538,6 +538,10 @@ export interface Person {
|
||||
city?: string
|
||||
}
|
||||
|
||||
export interface PersonInfo extends BasePerson {
|
||||
socialIds: PersonId[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -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<Ref<Doc>, string>()
|
||||
private readonly refMetaByPath = new Map<string, ReferenceMetadata>()
|
||||
private readonly fileMetaByPath = new Map<string, AttachmentMetadata>()
|
||||
private readonly ctrlDocTemplateIdByPath = new Map<string, Ref<ControlledDocument>>()
|
||||
|
||||
private personsByName = new Map<string, Ref<Person>>()
|
||||
// private accountsByEmail = new Map<string, Ref<PersonAccount>>()
|
||||
// private employeesByName = new Map<string, Ref<Employee>>()
|
||||
private employeesByName = new Map<string, Ref<Employee>>()
|
||||
|
||||
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<Person>
|
||||
) {}
|
||||
|
||||
private async initCaches (): Promise<void> {
|
||||
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<Employee> {
|
||||
// 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<void> {
|
||||
// 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<void> {
|
||||
// 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<void> {
|
||||
|
1
pods/workspace/.gitignore
vendored
Normal file
1
pods/workspace/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
init
|
@ -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<PersonInfo> {
|
||||
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<PersonUuid | undefined> {
|
||||
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<Record<AccountMe
|
||||
getLoginInfoByToken: wrap(getLoginInfoByToken),
|
||||
getSocialIds: wrap(getSocialIds),
|
||||
getPerson: wrap(getPerson),
|
||||
getPersonInfo: wrap(getPersonInfo),
|
||||
findPerson: wrap(findPerson),
|
||||
getWorkspaceMembers: wrap(getWorkspaceMembers),
|
||||
|
||||
|
@ -26,7 +26,8 @@ import {
|
||||
BackupStatus,
|
||||
type PersonUuid,
|
||||
type WorkspaceUuid,
|
||||
type WorkspaceDataId
|
||||
type WorkspaceDataId,
|
||||
type PersonId
|
||||
} from '@hcengineering/core'
|
||||
|
||||
/* ========= D A T A B A S E E N T I T I E S ========= */
|
||||
@ -43,7 +44,7 @@ export enum Location {
|
||||
export interface SocialId {
|
||||
type: SocialIdType
|
||||
value: string
|
||||
key: string // Calculated from type and value
|
||||
key: PersonId // Calculated from type and value
|
||||
personUuid: PersonUuid
|
||||
createdOn?: Timestamp
|
||||
verifiedOn?: Timestamp
|
||||
|
@ -27,7 +27,8 @@ import {
|
||||
systemAccountUuid,
|
||||
type WorkspaceInfoWithStatus as WorkspaceInfoWithStatusCore,
|
||||
isActiveMode,
|
||||
type PersonUuid
|
||||
type PersonUuid,
|
||||
type PersonId
|
||||
} from '@hcengineering/core'
|
||||
import { getMongoClient } from '@hcengineering/mongo' // TODO: get rid of this import later
|
||||
import platform, { getMetadata, PlatformError, Severity, Status, translate } from '@hcengineering/platform'
|
||||
@ -854,7 +855,7 @@ export async function getWorkspaceInvite (db: AccountDB, id: string): Promise<Wo
|
||||
return await db.invite.findOne({ migratedFrom: id })
|
||||
}
|
||||
|
||||
export async function getSocialIdByKey (db: AccountDB, socialKey: string): Promise<SocialId | null> {
|
||||
export async function getSocialIdByKey (db: AccountDB, socialKey: PersonId): Promise<SocialId | null> {
|
||||
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, {}))
|
||||
}
|
||||
}
|
||||
|
@ -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 })
|
||||
|
@ -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<void>
|
||||
): Promise<void> {
|
||||
const vars: Record<string, any> = {}
|
||||
const vars: Record<string, any> = {
|
||||
'${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<Ref<Class<Doc>>, Props<Doc>>()
|
||||
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)
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user