mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-31 04:38:02 +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": {
|
"env": {
|
||||||
"MONGO_URL": "mongodb://localhost:27017",
|
"MONGO_URL": "mongodb://localhost:27017",
|
||||||
"DB_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",
|
// "DB_URL": "postgresql://root@host.docker.internal:26257/defaultdb?sslmode=disable",
|
||||||
"SERVER_SECRET": "secret",
|
"SERVER_SECRET": "secret",
|
||||||
"REGION_INFO":"|Mongo;pg|Postgres;cockroach|CockroachDB",
|
"REGION_INFO":"|Mongo;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",
|
||||||
"ACCOUNTS_URL": "http://localhost:3000",
|
"ACCOUNTS_URL": "http://localhost:3000",
|
||||||
"ACCOUNT_PORT": "3000",
|
"ACCOUNT_PORT": "3000",
|
||||||
"FRONT_URL": "http://localhost:8080",
|
"FRONT_URL": "http://localhost:8080",
|
||||||
@ -154,8 +153,6 @@
|
|||||||
"MINIO_SECRET_KEY": "minioadmin",
|
"MINIO_SECRET_KEY": "minioadmin",
|
||||||
"MINIO_ENDPOINT": "localhost"
|
"MINIO_ENDPOINT": "localhost"
|
||||||
// "DISABLE_SIGNUP": "true",
|
// "DISABLE_SIGNUP": "true",
|
||||||
// "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml",
|
|
||||||
// "INIT_WORKSPACE": "onboarding",
|
|
||||||
},
|
},
|
||||||
"runtimeVersion": "20",
|
"runtimeVersion": "20",
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||||
@ -187,8 +184,6 @@
|
|||||||
"MINIO_SECRET_KEY": "minioadmin",
|
"MINIO_SECRET_KEY": "minioadmin",
|
||||||
"MINIO_ENDPOINT": "localhost"
|
"MINIO_ENDPOINT": "localhost"
|
||||||
// "DISABLE_SIGNUP": "true",
|
// "DISABLE_SIGNUP": "true",
|
||||||
// "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml",
|
|
||||||
// "INIT_WORKSPACE": "onboarding",
|
|
||||||
},
|
},
|
||||||
"runtimeVersion": "20",
|
"runtimeVersion": "20",
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||||
@ -236,8 +231,8 @@
|
|||||||
"WS_OPERATION": "all+backup",
|
"WS_OPERATION": "all+backup",
|
||||||
"BACKUP_STORAGE": "minio|minio?accessKey=minioadmin&secretKey=minioadmin",
|
"BACKUP_STORAGE": "minio|minio?accessKey=minioadmin&secretKey=minioadmin",
|
||||||
"BACKUP_BUCKET": "dev-backups",
|
"BACKUP_BUCKET": "dev-backups",
|
||||||
// "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml",
|
// "INIT_REPO_DIR": "${workspaceRoot}/pods/workspace/init",
|
||||||
// "INIT_WORKSPACE": "onboarding",
|
// "INIT_WORKSPACE": "staging-dev"
|
||||||
},
|
},
|
||||||
"runtimeVersion": "20",
|
"runtimeVersion": "20",
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||||
@ -269,8 +264,8 @@
|
|||||||
"WS_OPERATION": "all+backup",
|
"WS_OPERATION": "all+backup",
|
||||||
"BACKUP_STORAGE": "minio|minio?accessKey=minioadmin&secretKey=minioadmin",
|
"BACKUP_STORAGE": "minio|minio?accessKey=minioadmin&secretKey=minioadmin",
|
||||||
"BACKUP_BUCKET": "dev-backups",
|
"BACKUP_BUCKET": "dev-backups",
|
||||||
// "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml",
|
// "INIT_REPO_DIR": "${workspaceRoot}/pods/workspace/init",
|
||||||
// "INIT_WORKSPACE": "onboarding",
|
// "INIT_WORKSPACE": "staging-dev"
|
||||||
},
|
},
|
||||||
"runtimeVersion": "20",
|
"runtimeVersion": "20",
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||||
|
@ -80,7 +80,7 @@ services:
|
|||||||
# Pass only one region to disallow selection for new workspaces.Ø
|
# Pass only one region to disallow selection for new workspaces.Ø
|
||||||
- REGION_INFO=|Mongo;cockroach|CockroachDB
|
- REGION_INFO=|Mongo;cockroach|CockroachDB
|
||||||
# - REGION_INFO=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=
|
- SES_URL=
|
||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
- FRONT_URL=http://host.docker.internal:8087
|
- FRONT_URL=http://host.docker.internal:8087
|
||||||
@ -91,8 +91,6 @@ services:
|
|||||||
- ACCOUNTS_URL=http://host.docker.internal:3000
|
- ACCOUNTS_URL=http://host.docker.internal:3000
|
||||||
- BRANDING_PATH=/var/cfg/branding.json
|
- BRANDING_PATH=/var/cfg/branding.json
|
||||||
# - DISABLE_SIGNUP=true
|
# - DISABLE_SIGNUP=true
|
||||||
# - INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml
|
|
||||||
# - INIT_WORKSPACE=onboarding
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
stats:
|
stats:
|
||||||
image: hardcoreeng/stats
|
image: hardcoreeng/stats
|
||||||
@ -126,9 +124,9 @@ services:
|
|||||||
- ACCOUNTS_URL=http://host.docker.internal:3000
|
- ACCOUNTS_URL=http://host.docker.internal:3000
|
||||||
- BRANDING_PATH=/var/cfg/branding.json
|
- BRANDING_PATH=/var/cfg/branding.json
|
||||||
# - PARALLEL=2
|
# - PARALLEL=2
|
||||||
- INIT_WORKSPACE=test
|
|
||||||
- BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG}
|
- BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG}
|
||||||
- BACKUP_BUCKET=${BACKUP_BUCKET_NAME}
|
- BACKUP_BUCKET=${BACKUP_BUCKET_NAME}
|
||||||
|
# - INIT_WORKSPACE=staging-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
workspace_cockroach:
|
workspace_cockroach:
|
||||||
image: hardcoreeng/workspace
|
image: hardcoreeng/workspace
|
||||||
@ -153,9 +151,9 @@ services:
|
|||||||
- ACCOUNTS_URL=http://host.docker.internal:3000
|
- ACCOUNTS_URL=http://host.docker.internal:3000
|
||||||
- BRANDING_PATH=/var/cfg/branding.json
|
- BRANDING_PATH=/var/cfg/branding.json
|
||||||
# - PARALLEL=2
|
# - PARALLEL=2
|
||||||
# - INIT_WORKSPACE=onboarding
|
|
||||||
- BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG}
|
- BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG}
|
||||||
- BACKUP_BUCKET=${BACKUP_BUCKET_NAME}
|
- BACKUP_BUCKET=${BACKUP_BUCKET_NAME}
|
||||||
|
# - INIT_WORKSPACE=staging-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
collaborator:
|
collaborator:
|
||||||
image: hardcoreeng/collaborator
|
image: hardcoreeng/collaborator
|
||||||
|
@ -334,7 +334,7 @@ export async function moveAccountDbFromMongoToPG (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (workspacesCount % 100 === 0) {
|
if (workspacesCount % 100 === 0) {
|
||||||
ctx.info(`Migrated ${workspacesCount} invites...`)
|
ctx.info(`Migrated ${workspacesCount} workspaces...`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.info(`Migrated ${workspacesCount} workspaces with ${membersCount} member assignments`)
|
ctx.info(`Migrated ${workspacesCount} workspaces with ${membersCount} member assignments`)
|
||||||
|
@ -46,6 +46,7 @@ import {
|
|||||||
AccountRole,
|
AccountRole,
|
||||||
MeasureMetricsContext,
|
MeasureMetricsContext,
|
||||||
metricsToString,
|
metricsToString,
|
||||||
|
type PersonId,
|
||||||
type Data,
|
type Data,
|
||||||
type Tx,
|
type Tx,
|
||||||
type Version,
|
type Version,
|
||||||
@ -304,7 +305,7 @@ export function devTool (
|
|||||||
const measureCtx = new MeasureMetricsContext('create-workspace', {})
|
const measureCtx = new MeasureMetricsContext('create-workspace', {})
|
||||||
const brandingObj =
|
const brandingObj =
|
||||||
cmd.branding !== undefined || cmd.init !== undefined ? { key: cmd.branding, initWorkspace: cmd.init } : null
|
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) {
|
if (socialId == null) {
|
||||||
throw new Error(`Social id ${socialString} not found`)
|
throw new Error(`Social id ${socialString} not found`)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,8 @@ import {
|
|||||||
BackupStatus,
|
BackupStatus,
|
||||||
Data,
|
Data,
|
||||||
type Person,
|
type Person,
|
||||||
PersonUuid,
|
type PersonUuid,
|
||||||
|
type PersonInfo,
|
||||||
SocialId,
|
SocialId,
|
||||||
Version,
|
Version,
|
||||||
type WorkspaceInfoWithStatus,
|
type WorkspaceInfoWithStatus,
|
||||||
@ -73,6 +74,7 @@ export interface AccountClient {
|
|||||||
signUp: (email: string, password: string, first: string, last: string) => Promise<LoginInfo>
|
signUp: (email: string, password: string, first: string, last: string) => Promise<LoginInfo>
|
||||||
login: (email: string, password: string) => Promise<LoginInfo>
|
login: (email: string, password: string) => Promise<LoginInfo>
|
||||||
getPerson: () => Promise<Person>
|
getPerson: () => Promise<Person>
|
||||||
|
getPersonInfo: (account: PersonUuid) => Promise<PersonInfo>
|
||||||
getSocialIds: () => Promise<SocialId[]>
|
getSocialIds: () => Promise<SocialId[]>
|
||||||
getWorkspaceMembers: () => Promise<WorkspaceMemberInfo[]>
|
getWorkspaceMembers: () => Promise<WorkspaceMemberInfo[]>
|
||||||
updateWorkspaceRole: (account: string, role: AccountRole) => Promise<void>
|
updateWorkspaceRole: (account: string, role: AccountRole) => Promise<void>
|
||||||
@ -399,6 +401,15 @@ class AccountClientImpl implements AccountClient {
|
|||||||
return await this.rpc(request)
|
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[]> {
|
async getSocialIds (): Promise<SocialId[]> {
|
||||||
const request = {
|
const request = {
|
||||||
method: 'getSocialIds' as const,
|
method: 'getSocialIds' as const,
|
||||||
|
@ -538,6 +538,10 @@ export interface Person {
|
|||||||
city?: string
|
city?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PersonInfo extends BasePerson {
|
||||||
|
socialIds: PersonId[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
@ -16,11 +16,13 @@
|
|||||||
import { type Attachment } from '@hcengineering/attachment'
|
import { type Attachment } from '@hcengineering/attachment'
|
||||||
import contact, { Employee, type Person } from '@hcengineering/contact'
|
import contact, { Employee, type Person } from '@hcengineering/contact'
|
||||||
import {
|
import {
|
||||||
|
buildSocialIdString,
|
||||||
type Class,
|
type Class,
|
||||||
type Doc,
|
type Doc,
|
||||||
generateId,
|
generateId,
|
||||||
PersonId,
|
PersonId,
|
||||||
type Ref,
|
type Ref,
|
||||||
|
SocialIdType,
|
||||||
type Space,
|
type Space,
|
||||||
type TxOperations
|
type TxOperations
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
@ -330,24 +332,26 @@ interface AttachmentMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedFormatImporter {
|
export class UnifiedFormatImporter {
|
||||||
|
private readonly importerEmailPlaceholder = 'newuser@huly.io'
|
||||||
|
private readonly importerNamePlaceholder = 'New User'
|
||||||
private readonly pathById = new Map<Ref<Doc>, string>()
|
private readonly pathById = new Map<Ref<Doc>, string>()
|
||||||
private readonly refMetaByPath = new Map<string, ReferenceMetadata>()
|
private readonly refMetaByPath = new Map<string, ReferenceMetadata>()
|
||||||
private readonly fileMetaByPath = new Map<string, AttachmentMetadata>()
|
private readonly fileMetaByPath = new Map<string, AttachmentMetadata>()
|
||||||
private readonly ctrlDocTemplateIdByPath = new Map<string, Ref<ControlledDocument>>()
|
private readonly ctrlDocTemplateIdByPath = new Map<string, Ref<ControlledDocument>>()
|
||||||
|
|
||||||
private personsByName = new Map<string, Ref<Person>>()
|
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 (
|
constructor (
|
||||||
private readonly client: TxOperations,
|
private readonly client: TxOperations,
|
||||||
private readonly fileUploader: FileUploader,
|
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> {
|
private async initCaches (): Promise<void> {
|
||||||
await this.cachePersonsByNames()
|
await this.cachePersonsByNames()
|
||||||
await this.cacheAccountsByEmails()
|
|
||||||
await this.cacheEmployeesByName()
|
await this.cacheEmployeesByName()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,6 +592,10 @@ export class UnifiedFormatImporter {
|
|||||||
if (name === undefined) {
|
if (name === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === this.importerNamePlaceholder && this.importerPerson != null) {
|
||||||
|
return this.importerPerson
|
||||||
|
}
|
||||||
const person = this.personsByName.get(name)
|
const person = this.personsByName.get(name)
|
||||||
if (person === undefined) {
|
if (person === undefined) {
|
||||||
throw new Error(`Person not found: ${name}`)
|
throw new Error(`Person not found: ${name}`)
|
||||||
@ -595,24 +603,20 @@ export class UnifiedFormatImporter {
|
|||||||
return person
|
return person
|
||||||
}
|
}
|
||||||
|
|
||||||
private findAccountByEmail (email: string): PersonId {
|
private getSocialIdByEmail (email: string): PersonId {
|
||||||
// TODO: FIXME
|
if (email === this.importerEmailPlaceholder && this.importerSocialId != null) {
|
||||||
throw new Error('Not implemented')
|
return this.importerSocialId
|
||||||
// const account = this.accountsByEmail.get(email)
|
}
|
||||||
// if (account === undefined) {
|
|
||||||
// throw new Error(`Account not found: ${email}`)
|
return buildSocialIdString({ type: SocialIdType.EMAIL, value: email })
|
||||||
// }
|
|
||||||
// return account
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private findEmployeeByName (name: string): Ref<Employee> {
|
private findEmployeeByName (name: string): Ref<Employee> {
|
||||||
// TODO: FIXME
|
const employee = this.employeesByName.get(name)
|
||||||
throw new Error('Not implemented')
|
if (employee === undefined) {
|
||||||
// const employee = this.employeesByName.get(name)
|
throw new Error(`Employee not found: ${name}`)
|
||||||
// if (employee === undefined) {
|
}
|
||||||
// throw new Error(`Employee not found: ${name}`)
|
return employee
|
||||||
// }
|
|
||||||
// return employee
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processDocumentsRecursively (
|
private async processDocumentsRecursively (
|
||||||
@ -752,7 +756,7 @@ export class UnifiedFormatImporter {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
text: comment.text,
|
text: comment.text,
|
||||||
author: this.findAccountByEmail(comment.author),
|
author: this.getSocialIdByEmail(comment.author),
|
||||||
attachments
|
attachments
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -789,9 +793,9 @@ export class UnifiedFormatImporter {
|
|||||||
defaultIssueStatus:
|
defaultIssueStatus:
|
||||||
projectHeader.defaultIssueStatus !== undefined ? { name: projectHeader.defaultIssueStatus } : undefined,
|
projectHeader.defaultIssueStatus !== undefined ? { name: projectHeader.defaultIssueStatus } : undefined,
|
||||||
owners:
|
owners:
|
||||||
projectHeader.owners !== undefined ? projectHeader.owners.map((email) => this.findAccountByEmail(email)) : [],
|
projectHeader.owners !== undefined ? projectHeader.owners.map((email) => this.getSocialIdByEmail(email)) : [],
|
||||||
members:
|
members:
|
||||||
projectHeader.members !== undefined ? projectHeader.members.map((email) => this.findAccountByEmail(email)) : [],
|
projectHeader.members !== undefined ? projectHeader.members.map((email) => this.getSocialIdByEmail(email)) : [],
|
||||||
docs: []
|
docs: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -805,9 +809,9 @@ export class UnifiedFormatImporter {
|
|||||||
archived: spaceHeader.archived ?? false,
|
archived: spaceHeader.archived ?? false,
|
||||||
description: spaceHeader.description,
|
description: spaceHeader.description,
|
||||||
emoji: spaceHeader.emoji,
|
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:
|
members:
|
||||||
spaceHeader.members !== undefined ? spaceHeader.members.map((email) => this.findAccountByEmail(email)) : [],
|
spaceHeader.members !== undefined ? spaceHeader.members.map((email) => this.getSocialIdByEmail(email)) : [],
|
||||||
docs: []
|
docs: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -819,11 +823,11 @@ export class UnifiedFormatImporter {
|
|||||||
private: spaceHeader.private ?? false,
|
private: spaceHeader.private ?? false,
|
||||||
archived: spaceHeader.archived ?? false,
|
archived: spaceHeader.archived ?? false,
|
||||||
description: spaceHeader.description,
|
description: spaceHeader.description,
|
||||||
owners: spaceHeader.owners?.map((email) => this.findAccountByEmail(email)) ?? [],
|
owners: spaceHeader.owners?.map((email) => this.getSocialIdByEmail(email)) ?? [],
|
||||||
members: spaceHeader.members?.map((email) => this.findAccountByEmail(email)) ?? [],
|
members: spaceHeader.members?.map((email) => this.getSocialIdByEmail(email)) ?? [],
|
||||||
qualified: spaceHeader.qualified !== undefined ? this.findAccountByEmail(spaceHeader.qualified) : undefined,
|
qualified: spaceHeader.qualified !== undefined ? this.getSocialIdByEmail(spaceHeader.qualified) : undefined,
|
||||||
manager: spaceHeader.manager !== undefined ? this.findAccountByEmail(spaceHeader.manager) : undefined,
|
manager: spaceHeader.manager !== undefined ? this.getSocialIdByEmail(spaceHeader.manager) : undefined,
|
||||||
qara: spaceHeader.qara !== undefined ? this.findAccountByEmail(spaceHeader.qara) : undefined,
|
qara: spaceHeader.qara !== undefined ? this.getSocialIdByEmail(spaceHeader.qara) : undefined,
|
||||||
docs: []
|
docs: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -950,30 +954,18 @@ export class UnifiedFormatImporter {
|
|||||||
}, new Map())
|
}, 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> {
|
private async cacheEmployeesByName (): Promise<void> {
|
||||||
// TODO: FIXME
|
this.employeesByName = (await this.client.findAll(contact.mixin.Employee, {}))
|
||||||
throw new Error('Not implemented')
|
.map((employee) => {
|
||||||
// this.employeesByName = (await this.client.findAll(contact.mixin.Employee, {}))
|
return {
|
||||||
// .map((employee) => {
|
_id: employee._id,
|
||||||
// return {
|
name: employee.name.split(',').reverse().join(' ')
|
||||||
// _id: employee._id,
|
}
|
||||||
// name: employee.name.split(',').reverse().join(' ')
|
})
|
||||||
// }
|
.reduce((refByName, employee) => {
|
||||||
// })
|
refByName.set(employee.name, employee._id)
|
||||||
// .reduce((refByName, employee) => {
|
return refByName
|
||||||
// refByName.set(employee.name, employee._id)
|
}, new Map())
|
||||||
// return refByName
|
|
||||||
// }, new Map())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async collectFileMetadata (folderPath: string): Promise<void> {
|
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 Branding,
|
||||||
type Person,
|
type Person,
|
||||||
type PersonUuid,
|
type PersonUuid,
|
||||||
|
type PersonInfo,
|
||||||
type WorkspaceMemberInfo,
|
type WorkspaceMemberInfo,
|
||||||
type WorkspaceMode,
|
type WorkspaceMode,
|
||||||
type WorkspaceUuid
|
type WorkspaceUuid,
|
||||||
|
type PersonId
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import platform, {
|
import platform, {
|
||||||
getMetadata,
|
getMetadata,
|
||||||
@ -86,7 +88,8 @@ import {
|
|||||||
setPassword,
|
setPassword,
|
||||||
signUpByEmail,
|
signUpByEmail,
|
||||||
verifyPassword,
|
verifyPassword,
|
||||||
wrap
|
wrap,
|
||||||
|
verifyAllowedServices
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
// Move to config?
|
// Move to config?
|
||||||
@ -1052,6 +1055,31 @@ export async function getPerson (
|
|||||||
return person
|
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 (
|
export async function findPerson (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
db: AccountDB,
|
db: AccountDB,
|
||||||
@ -1061,7 +1089,7 @@ export async function findPerson (
|
|||||||
): Promise<PersonUuid | undefined> {
|
): Promise<PersonUuid | undefined> {
|
||||||
decodeTokenVerbose(ctx, token)
|
decodeTokenVerbose(ctx, token)
|
||||||
|
|
||||||
const socialId = await db.socialId.findOne({ key: socialString })
|
const socialId = await db.socialId.findOne({ key: socialString as PersonId })
|
||||||
|
|
||||||
if (socialId == null) {
|
if (socialId == null) {
|
||||||
return
|
return
|
||||||
@ -1105,7 +1133,7 @@ export async function updateWorkspaceRoleBySocialId (
|
|||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
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) {
|
if (socialId == null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||||
}
|
}
|
||||||
@ -1452,6 +1480,7 @@ export type AccountMethods =
|
|||||||
| 'updateBackupInfo'
|
| 'updateBackupInfo'
|
||||||
| 'assignWorkspace'
|
| 'assignWorkspace'
|
||||||
| 'getPerson'
|
| 'getPerson'
|
||||||
|
| 'getPersonInfo'
|
||||||
| 'getWorkspaceMembers'
|
| 'getWorkspaceMembers'
|
||||||
| 'updateWorkspaceRole'
|
| 'updateWorkspaceRole'
|
||||||
| 'findPerson'
|
| 'findPerson'
|
||||||
@ -1493,6 +1522,7 @@ export function getMethods (hasSignUp: boolean = true): Partial<Record<AccountMe
|
|||||||
getLoginInfoByToken: wrap(getLoginInfoByToken),
|
getLoginInfoByToken: wrap(getLoginInfoByToken),
|
||||||
getSocialIds: wrap(getSocialIds),
|
getSocialIds: wrap(getSocialIds),
|
||||||
getPerson: wrap(getPerson),
|
getPerson: wrap(getPerson),
|
||||||
|
getPersonInfo: wrap(getPersonInfo),
|
||||||
findPerson: wrap(findPerson),
|
findPerson: wrap(findPerson),
|
||||||
getWorkspaceMembers: wrap(getWorkspaceMembers),
|
getWorkspaceMembers: wrap(getWorkspaceMembers),
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ import {
|
|||||||
BackupStatus,
|
BackupStatus,
|
||||||
type PersonUuid,
|
type PersonUuid,
|
||||||
type WorkspaceUuid,
|
type WorkspaceUuid,
|
||||||
type WorkspaceDataId
|
type WorkspaceDataId,
|
||||||
|
type PersonId
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
|
|
||||||
/* ========= D A T A B A S E E N T I T I E S ========= */
|
/* ========= 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 {
|
export interface SocialId {
|
||||||
type: SocialIdType
|
type: SocialIdType
|
||||||
value: string
|
value: string
|
||||||
key: string // Calculated from type and value
|
key: PersonId // Calculated from type and value
|
||||||
personUuid: PersonUuid
|
personUuid: PersonUuid
|
||||||
createdOn?: Timestamp
|
createdOn?: Timestamp
|
||||||
verifiedOn?: Timestamp
|
verifiedOn?: Timestamp
|
||||||
|
@ -27,7 +27,8 @@ import {
|
|||||||
systemAccountUuid,
|
systemAccountUuid,
|
||||||
type WorkspaceInfoWithStatus as WorkspaceInfoWithStatusCore,
|
type WorkspaceInfoWithStatus as WorkspaceInfoWithStatusCore,
|
||||||
isActiveMode,
|
isActiveMode,
|
||||||
type PersonUuid
|
type PersonUuid,
|
||||||
|
type PersonId
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { getMongoClient } from '@hcengineering/mongo' // TODO: get rid of this import later
|
import { getMongoClient } from '@hcengineering/mongo' // TODO: get rid of this import later
|
||||||
import platform, { getMetadata, PlatformError, Severity, Status, translate } from '@hcengineering/platform'
|
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 })
|
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 })
|
return await db.socialId.findOne({ key: socialKey })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1106,3 +1107,9 @@ export async function getWorkspaces (
|
|||||||
status: statusesMap[it.uuid]
|
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 Client,
|
||||||
type Ref,
|
type Ref,
|
||||||
type WithLookup,
|
type WithLookup,
|
||||||
type WorkspaceDataId
|
type WorkspaceDataId,
|
||||||
|
type PersonInfo
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { consoleModelLogger, MigrateOperation, ModelLogger, tryMigrate } from '@hcengineering/model'
|
import { consoleModelLogger, MigrateOperation, ModelLogger, tryMigrate } from '@hcengineering/model'
|
||||||
import { DomainIndexHelperImpl, Pipeline, StorageAdapter, type DbAdapter } from '@hcengineering/server-core'
|
import { DomainIndexHelperImpl, Pipeline, StorageAdapter, type DbAdapter } from '@hcengineering/server-core'
|
||||||
@ -187,6 +188,7 @@ export async function initializeWorkspace (
|
|||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
branding: Branding | null,
|
branding: Branding | null,
|
||||||
wsIds: WorkspaceIds,
|
wsIds: WorkspaceIds,
|
||||||
|
personInfo: PersonInfo,
|
||||||
storageAdapter: StorageAdapter,
|
storageAdapter: StorageAdapter,
|
||||||
client: TxOperations,
|
client: TxOperations,
|
||||||
logger: ModelLogger = consoleModelLogger,
|
logger: ModelLogger = consoleModelLogger,
|
||||||
@ -218,7 +220,7 @@ export async function initializeWorkspace (
|
|||||||
return
|
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)
|
await initializer.processScript(script, logger, progress)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ctx.error('Failed to initialize workspace', { error: err })
|
ctx.error('Failed to initialize workspace', { error: err })
|
||||||
|
@ -8,7 +8,11 @@ import core, {
|
|||||||
makeCollabId,
|
makeCollabId,
|
||||||
MeasureContext,
|
MeasureContext,
|
||||||
Mixin,
|
Mixin,
|
||||||
|
parseSocialIdString,
|
||||||
|
type PersonId,
|
||||||
|
type PersonInfo,
|
||||||
Ref,
|
Ref,
|
||||||
|
SocialIdType,
|
||||||
Space,
|
Space,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
type WorkspaceDataId,
|
type WorkspaceDataId,
|
||||||
@ -19,6 +23,7 @@ import { makeRank } from '@hcengineering/rank'
|
|||||||
import { StorageFileUploader, UnifiedFormatImporter } from '@hcengineering/importer'
|
import { StorageFileUploader, UnifiedFormatImporter } from '@hcengineering/importer'
|
||||||
import type { StorageAdapter } from '@hcengineering/server-core'
|
import type { StorageAdapter } from '@hcengineering/server-core'
|
||||||
import { jsonToMarkup, parseMessageMarkdown } from '@hcengineering/text'
|
import { jsonToMarkup, parseMessageMarkdown } from '@hcengineering/text'
|
||||||
|
import { pickPrimarySocialId } from '@hcengineering/contact'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
@ -96,21 +101,38 @@ export class WorkspaceInitializer {
|
|||||||
private readonly imageUrl = 'image://'
|
private readonly imageUrl = 'image://'
|
||||||
private readonly nextRank = '#nextRank'
|
private readonly nextRank = '#nextRank'
|
||||||
private readonly now = '#now'
|
private readonly now = '#now'
|
||||||
|
private readonly creatorPersonVar = 'creatorPerson'
|
||||||
|
private readonly socialKey: PersonId
|
||||||
|
private readonly socialType: SocialIdType
|
||||||
|
private readonly socialValue: string
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly ctx: MeasureContext,
|
private readonly ctx: MeasureContext,
|
||||||
private readonly storageAdapter: StorageAdapter,
|
private readonly storageAdapter: StorageAdapter,
|
||||||
private readonly wsIds: WorkspaceIds,
|
private readonly wsIds: WorkspaceIds,
|
||||||
private readonly client: TxOperations,
|
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 (
|
async processScript (
|
||||||
script: InitScript,
|
script: InitScript,
|
||||||
logger: ModelLogger,
|
logger: ModelLogger,
|
||||||
progress: (value: number) => Promise<void>
|
progress: (value: number) => Promise<void>
|
||||||
): 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>>()
|
const defaults = new Map<Ref<Class<Doc>>, Props<Doc>>()
|
||||||
for (let index = 0; index < script.steps.length; index++) {
|
for (let index = 0; index < script.steps.length; index++) {
|
||||||
try {
|
try {
|
||||||
@ -169,7 +191,9 @@ export class WorkspaceInitializer {
|
|||||||
try {
|
try {
|
||||||
const uploader = new StorageFileUploader(this.ctx, this.storageAdapter, this.wsIds)
|
const uploader = new StorageFileUploader(this.ctx, this.storageAdapter, this.wsIds)
|
||||||
const initPath = path.resolve(this.initRepoDir, step.path)
|
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)
|
await importer.importFolder(initPath)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Import failed', error)
|
logger.error('Import failed', error)
|
||||||
|
@ -101,10 +101,31 @@ export async function createWorkspace (
|
|||||||
})
|
})
|
||||||
|
|
||||||
ctx.info('Starting init script if any')
|
ctx.info('Starting init script if any')
|
||||||
await initializeWorkspace(ctx, branding, wsIds, storageAdapter, client, ctxModellogger, async (value) => {
|
const creatorUuid = workspaceInfo.createdBy
|
||||||
ctx.info('Init script progress', { value })
|
|
||||||
await handleWsEvent?.('progress', version, 20 + Math.round((Math.min(value, 100) / 100) * 60))
|
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(
|
await upgradeWorkspaceWith(
|
||||||
ctx,
|
ctx,
|
||||||
|
Loading…
Reference in New Issue
Block a user