mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
UBERF-6598: Perform upgrade all workspaces to new versions (#5392)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
4536ad8355
commit
808b5c8f7d
@ -30,10 +30,8 @@ import {
|
|||||||
replacePassword,
|
replacePassword,
|
||||||
setAccountAdmin,
|
setAccountAdmin,
|
||||||
setRole,
|
setRole,
|
||||||
updateWorkspace,
|
|
||||||
upgradeWorkspace,
|
upgradeWorkspace,
|
||||||
type Workspace,
|
UpgradeWorker
|
||||||
type WorkspaceInfo
|
|
||||||
} from '@hcengineering/account'
|
} from '@hcengineering/account'
|
||||||
import { setMetadata } from '@hcengineering/platform'
|
import { setMetadata } from '@hcengineering/platform'
|
||||||
import {
|
import {
|
||||||
@ -45,7 +43,7 @@ import {
|
|||||||
restore
|
restore
|
||||||
} from '@hcengineering/server-backup'
|
} from '@hcengineering/server-backup'
|
||||||
import serverToken, { decodeToken, generateToken } from '@hcengineering/server-token'
|
import serverToken, { decodeToken, generateToken } from '@hcengineering/server-token'
|
||||||
import toolPlugin, { FileModelLogger } from '@hcengineering/server-tool'
|
import toolPlugin from '@hcengineering/server-tool'
|
||||||
|
|
||||||
import { program, type Command } from 'commander'
|
import { program, type Command } from 'commander'
|
||||||
import { type Db, type MongoClient } from 'mongodb'
|
import { type Db, type MongoClient } from 'mongodb'
|
||||||
@ -56,7 +54,6 @@ import core, {
|
|||||||
getWorkspaceId,
|
getWorkspaceId,
|
||||||
MeasureMetricsContext,
|
MeasureMetricsContext,
|
||||||
metricsToString,
|
metricsToString,
|
||||||
RateLimiter,
|
|
||||||
versionToString,
|
versionToString,
|
||||||
type AccountRole,
|
type AccountRole,
|
||||||
type Data,
|
type Data,
|
||||||
@ -69,7 +66,6 @@ import { getMongoClient, getWorkspaceDB } from '@hcengineering/mongo'
|
|||||||
import { openAIConfigDefaults } from '@hcengineering/openai'
|
import { openAIConfigDefaults } from '@hcengineering/openai'
|
||||||
import { type StorageAdapter } from '@hcengineering/server-core'
|
import { type StorageAdapter } from '@hcengineering/server-core'
|
||||||
import { deepEqual } from 'fast-equals'
|
import { deepEqual } from 'fast-equals'
|
||||||
import path from 'path'
|
|
||||||
import { benchmark } from './benchmark'
|
import { benchmark } from './benchmark'
|
||||||
import {
|
import {
|
||||||
cleanArchivedSpaces,
|
cleanArchivedSpaces,
|
||||||
@ -345,93 +341,15 @@ export function devTool (
|
|||||||
.option('-f|--force [force]', 'Force update', false)
|
.option('-f|--force [force]', 'Force update', false)
|
||||||
.action(async (cmd: { parallel: string, logs: string, retry: string, force: boolean, console: boolean }) => {
|
.action(async (cmd: { parallel: string, logs: string, retry: string, force: boolean, console: boolean }) => {
|
||||||
const { mongodbUri, version, txes, migrateOperations } = prepareTools()
|
const { mongodbUri, version, txes, migrateOperations } = prepareTools()
|
||||||
await withDatabase(mongodbUri, async (db) => {
|
await withDatabase(mongodbUri, async (db, client) => {
|
||||||
const workspaces = await listWorkspacesRaw(db, productId)
|
const worker = new UpgradeWorker(db, client, version, txes, migrateOperations, productId)
|
||||||
workspaces.sort((a, b) => b.lastVisit - a.lastVisit)
|
await worker.upgradeAll(toolCtx, {
|
||||||
|
errorHandler: async (ws, err) => {},
|
||||||
// We need to update workspaces with missing workspaceUrl
|
force: cmd.force,
|
||||||
for (const ws of workspaces) {
|
console: cmd.console,
|
||||||
if (ws.workspaceUrl == null) {
|
logs: cmd.logs,
|
||||||
const upd: Partial<Workspace> = {
|
parallel: parseInt(cmd.parallel ?? '1')
|
||||||
workspaceUrl: ws.workspace
|
|
||||||
}
|
|
||||||
if (ws.workspaceName == null) {
|
|
||||||
upd.workspaceName = ws.workspace
|
|
||||||
}
|
|
||||||
await updateWorkspace(db, productId, ws, upd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const withError: string[] = []
|
|
||||||
let toProcess = workspaces.length
|
|
||||||
const st = Date.now()
|
|
||||||
|
|
||||||
async function _upgradeWorkspace (ws: WorkspaceInfo): Promise<void> {
|
|
||||||
if (ws.disabled === true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const t = Date.now()
|
|
||||||
const logger = cmd.console
|
|
||||||
? consoleModelLogger
|
|
||||||
: new FileModelLogger(path.join(cmd.logs, `${ws.workspace}.log`))
|
|
||||||
|
|
||||||
const avgTime = (Date.now() - st) / (workspaces.length - toProcess + 1)
|
|
||||||
console.log(
|
|
||||||
'----------------------------------------------------------\n---UPGRADING----',
|
|
||||||
'pending: ',
|
|
||||||
toProcess,
|
|
||||||
'ETA: ',
|
|
||||||
Math.floor(avgTime * toProcess),
|
|
||||||
ws.workspace
|
|
||||||
)
|
|
||||||
toProcess--
|
|
||||||
try {
|
|
||||||
await upgradeWorkspace(
|
|
||||||
toolCtx,
|
|
||||||
version,
|
|
||||||
txes,
|
|
||||||
migrateOperations,
|
|
||||||
productId,
|
|
||||||
db,
|
|
||||||
ws.workspaceUrl ?? ws.workspace,
|
|
||||||
logger,
|
|
||||||
cmd.force
|
|
||||||
)
|
|
||||||
console.log('---done---------', 'pending: ', toProcess, 'TIME:', Date.now() - t, ws.workspace)
|
|
||||||
} catch (err: any) {
|
|
||||||
withError.push(ws.workspace)
|
|
||||||
logger.log('error', JSON.stringify(err))
|
|
||||||
console.log(' FAILED-------', 'pending: ', toProcess, 'TIME:', Date.now() - t, ws.workspace)
|
|
||||||
} finally {
|
|
||||||
if (!cmd.console) {
|
|
||||||
;(logger as FileModelLogger).close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cmd.parallel !== '0') {
|
|
||||||
const parallel = parseInt(cmd.parallel) ?? 1
|
|
||||||
const rateLimit = new RateLimiter(parallel)
|
|
||||||
console.log('parallel upgrade', parallel, cmd.parallel)
|
|
||||||
await Promise.all(
|
|
||||||
workspaces.map((it) =>
|
|
||||||
rateLimit.add(() => {
|
|
||||||
return _upgradeWorkspace(it)
|
|
||||||
})
|
})
|
||||||
)
|
|
||||||
)
|
|
||||||
console.log('Upgrade done')
|
|
||||||
// console.log((process as any)._getActiveHandles())
|
|
||||||
// console.log((process as any)._getActiveRequests())
|
|
||||||
// process.exit()
|
|
||||||
} else {
|
|
||||||
console.log('UPGRADE write logs at:', cmd.logs)
|
|
||||||
for (const ws of workspaces) {
|
|
||||||
await _upgradeWorkspace(ws)
|
|
||||||
}
|
|
||||||
if (withError.length > 0) {
|
|
||||||
console.log('Failed workspaces', withError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ import contact, { contactId, createModel as contactModel } from '@hcengineering/
|
|||||||
import { createModel as coreModel } from '@hcengineering/model-core'
|
import { createModel as coreModel } from '@hcengineering/model-core'
|
||||||
import document, { documentId, createModel as documentModel } from '@hcengineering/model-document'
|
import document, { documentId, createModel as documentModel } from '@hcengineering/model-document'
|
||||||
import gmail, { gmailId, createModel as gmailModel } from '@hcengineering/model-gmail'
|
import gmail, { gmailId, createModel as gmailModel } from '@hcengineering/model-gmail'
|
||||||
|
import { guestId, createModel as guestModel } from '@hcengineering/model-guest'
|
||||||
import hr, { hrId, createModel as hrModel } from '@hcengineering/model-hr'
|
import hr, { hrId, createModel as hrModel } from '@hcengineering/model-hr'
|
||||||
import { timeId, createModel as timeModel } from '@hcengineering/model-time'
|
|
||||||
import inventory, { inventoryId, createModel as inventoryModel } from '@hcengineering/model-inventory'
|
import inventory, { inventoryId, createModel as inventoryModel } from '@hcengineering/model-inventory'
|
||||||
import lead, { leadId, createModel as leadModel } from '@hcengineering/model-lead'
|
import lead, { leadId, createModel as leadModel } from '@hcengineering/model-lead'
|
||||||
import notification, { notificationId, createModel as notificationModel } from '@hcengineering/model-notification'
|
import notification, { notificationId, createModel as notificationModel } from '@hcengineering/model-notification'
|
||||||
@ -37,16 +37,17 @@ import recruit, { recruitId, createModel as recruitModel } from '@hcengineering/
|
|||||||
import { requestId, createModel as requestModel } from '@hcengineering/model-request'
|
import { requestId, createModel as requestModel } from '@hcengineering/model-request'
|
||||||
import { serverActivityId, createModel as serverActivityModel } from '@hcengineering/model-server-activity'
|
import { serverActivityId, createModel as serverActivityModel } from '@hcengineering/model-server-activity'
|
||||||
import { serverAttachmentId, createModel as serverAttachmentModel } from '@hcengineering/model-server-attachment'
|
import { serverAttachmentId, createModel as serverAttachmentModel } from '@hcengineering/model-server-attachment'
|
||||||
|
import { serverCalendarId, createModel as serverCalendarModel } from '@hcengineering/model-server-calendar'
|
||||||
|
import { serverChunterId, createModel as serverChunterModel } from '@hcengineering/model-server-chunter'
|
||||||
import {
|
import {
|
||||||
serverCollaborationId,
|
serverCollaborationId,
|
||||||
createModel as serverCollaborationModel
|
createModel as serverCollaborationModel
|
||||||
} from '@hcengineering/model-server-collaboration'
|
} from '@hcengineering/model-server-collaboration'
|
||||||
import { serverCalendarId, createModel as serverCalendarModel } from '@hcengineering/model-server-calendar'
|
|
||||||
import { serverChunterId, createModel as serverChunterModel } from '@hcengineering/model-server-chunter'
|
|
||||||
import { serverContactId, createModel as serverContactModel } from '@hcengineering/model-server-contact'
|
import { serverContactId, createModel as serverContactModel } from '@hcengineering/model-server-contact'
|
||||||
import { serverCoreId, createModel as serverCoreModel } from '@hcengineering/model-server-core'
|
import { serverCoreId, createModel as serverCoreModel } from '@hcengineering/model-server-core'
|
||||||
import { serverDocumentId, createModel as serverDocumentModel } from '@hcengineering/model-server-document'
|
import { serverDocumentId, createModel as serverDocumentModel } from '@hcengineering/model-server-document'
|
||||||
import { serverGmailId, createModel as serverGmailModel } from '@hcengineering/model-server-gmail'
|
import { serverGmailId, createModel as serverGmailModel } from '@hcengineering/model-server-gmail'
|
||||||
|
import { serverGuestId, createModel as serverGuestModel } from '@hcengineering/model-server-guest'
|
||||||
import { serverHrId, createModel as serverHrModel } from '@hcengineering/model-server-hr'
|
import { serverHrId, createModel as serverHrModel } from '@hcengineering/model-server-hr'
|
||||||
import { serverInventoryId, createModel as serverInventoryModel } from '@hcengineering/model-server-inventory'
|
import { serverInventoryId, createModel as serverInventoryModel } from '@hcengineering/model-server-inventory'
|
||||||
import { serverLeadId, createModel as serverLeadModel } from '@hcengineering/model-server-lead'
|
import { serverLeadId, createModel as serverLeadModel } from '@hcengineering/model-server-lead'
|
||||||
@ -58,6 +59,7 @@ import { serverTagsId, createModel as serverTagsModel } from '@hcengineering/mod
|
|||||||
import { serverTaskId, createModel as serverTaskModel } from '@hcengineering/model-server-task'
|
import { serverTaskId, createModel as serverTaskModel } from '@hcengineering/model-server-task'
|
||||||
import { serverTelegramId, createModel as serverTelegramModel } from '@hcengineering/model-server-telegram'
|
import { serverTelegramId, createModel as serverTelegramModel } from '@hcengineering/model-server-telegram'
|
||||||
import { serverTemplatesId, createModel as serverTemplatesModel } from '@hcengineering/model-server-templates'
|
import { serverTemplatesId, createModel as serverTemplatesModel } from '@hcengineering/model-server-templates'
|
||||||
|
import { serverTimeId, createModel as serverTimeModel } from '@hcengineering/model-server-time'
|
||||||
import { serverTrackerId, createModel as serverTrackerModel } from '@hcengineering/model-server-tracker'
|
import { serverTrackerId, createModel as serverTrackerModel } from '@hcengineering/model-server-tracker'
|
||||||
import { serverViewId, createModel as serverViewModel } from '@hcengineering/model-server-view'
|
import { serverViewId, createModel as serverViewModel } from '@hcengineering/model-server-view'
|
||||||
import setting, { settingId, createModel as settingModel } from '@hcengineering/model-setting'
|
import setting, { settingId, createModel as settingModel } from '@hcengineering/model-setting'
|
||||||
@ -67,12 +69,10 @@ import { taskId, createModel as taskModel } from '@hcengineering/model-task'
|
|||||||
import telegram, { telegramId, createModel as telegramModel } from '@hcengineering/model-telegram'
|
import telegram, { telegramId, createModel as telegramModel } from '@hcengineering/model-telegram'
|
||||||
import { templatesId, createModel as templatesModel } from '@hcengineering/model-templates'
|
import { templatesId, createModel as templatesModel } from '@hcengineering/model-templates'
|
||||||
import { textEditorId, createModel as textEditorModel } from '@hcengineering/model-text-editor'
|
import { textEditorId, createModel as textEditorModel } from '@hcengineering/model-text-editor'
|
||||||
|
import { timeId, createModel as timeModel } from '@hcengineering/model-time'
|
||||||
import tracker, { trackerId, createModel as trackerModel } from '@hcengineering/model-tracker'
|
import tracker, { trackerId, createModel as trackerModel } from '@hcengineering/model-tracker'
|
||||||
import view, { viewId, createModel as viewModel } from '@hcengineering/model-view'
|
import view, { viewId, createModel as viewModel } from '@hcengineering/model-view'
|
||||||
import workbench, { workbenchId, createModel as workbenchModel } from '@hcengineering/model-workbench'
|
import workbench, { workbenchId, createModel as workbenchModel } from '@hcengineering/model-workbench'
|
||||||
import { guestId, createModel as guestModel } from '@hcengineering/model-guest'
|
|
||||||
import { serverGuestId, createModel as serverGuestModel } from '@hcengineering/model-server-guest'
|
|
||||||
import { serverTimeId, createModel as serverTimeModel } from '@hcengineering/model-server-time'
|
|
||||||
|
|
||||||
import { openAIId, createModel as serverOpenAI } from '@hcengineering/model-server-openai'
|
import { openAIId, createModel as serverOpenAI } from '@hcengineering/model-server-openai'
|
||||||
import { createModel as serverTranslate, translateId } from '@hcengineering/model-server-translate'
|
import { createModel as serverTranslate, translateId } from '@hcengineering/model-server-translate'
|
||||||
@ -95,6 +95,8 @@ export function getModelVersion (): Data<Version> {
|
|||||||
return { major: 0, minor: 6, patch: 0 }
|
return { major: 0, minor: 6, patch: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type { MigrateOperation } from '@hcengineering/model'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param enabled - a set of enabled plugins
|
* @param enabled - a set of enabled plugins
|
||||||
|
@ -26,11 +26,9 @@ export const coreOperation: MigrateOperation = {
|
|||||||
// We need to delete all documents in doc index state for missing classes
|
// We need to delete all documents in doc index state for missing classes
|
||||||
const allClasses = client.hierarchy.getDescendants(core.class.Doc)
|
const allClasses = client.hierarchy.getDescendants(core.class.Doc)
|
||||||
const allIndexed = allClasses.filter((it) => isClassIndexable(client.hierarchy, it))
|
const allIndexed = allClasses.filter((it) => isClassIndexable(client.hierarchy, it))
|
||||||
const indexed = new Set(allIndexed)
|
|
||||||
const skipped = allClasses.filter((it) => !indexed.has(it))
|
|
||||||
|
|
||||||
// Next remove all non indexed classes and missing classes as well.
|
// Next remove all non indexed classes and missing classes as well.
|
||||||
const updated = await client.update(
|
await client.update(
|
||||||
DOMAIN_DOC_INDEX_STATE,
|
DOMAIN_DOC_INDEX_STATE,
|
||||||
{ objectClass: { $nin: allIndexed } },
|
{ objectClass: { $nin: allIndexed } },
|
||||||
{
|
{
|
||||||
@ -39,7 +37,6 @@ export const coreOperation: MigrateOperation = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
console.log('clearing non indexed documents', skipped, updated.updated, updated.matched)
|
|
||||||
},
|
},
|
||||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||||
await tryUpgrade(client, coreId, [
|
await tryUpgrade(client, coreId, [
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { getMethods } from '@hcengineering/account'
|
|
||||||
import { MeasureMetricsContext, newMetrics, type Tx } from '@hcengineering/core'
|
import { MeasureMetricsContext, newMetrics, type Tx } from '@hcengineering/core'
|
||||||
import builder, { getModelVersion, migrateOperations } from '@hcengineering/model-all'
|
import builder, { getModelVersion, migrateOperations } from '@hcengineering/model-all'
|
||||||
import { serveAccount } from '.'
|
import { serveAccount } from '.'
|
||||||
@ -25,4 +24,4 @@ const txes = JSON.parse(JSON.stringify(builder(enabled, disabled).getTxes())) as
|
|||||||
|
|
||||||
const metricsContext = new MeasureMetricsContext('account', {}, {}, newMetrics())
|
const metricsContext = new MeasureMetricsContext('account', {}, {}, newMetrics())
|
||||||
|
|
||||||
serveAccount(metricsContext, getMethods(getModelVersion(), txes, migrateOperations))
|
serveAccount(metricsContext, getModelVersion(), txes, migrateOperations)
|
||||||
|
@ -14,11 +14,18 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import account, { ACCOUNT_DB, type AccountMethod, accountId, cleanInProgressWorkspaces } from '@hcengineering/account'
|
import account, {
|
||||||
|
ACCOUNT_DB,
|
||||||
|
UpgradeWorker,
|
||||||
|
accountId,
|
||||||
|
cleanInProgressWorkspaces,
|
||||||
|
getMethods
|
||||||
|
} from '@hcengineering/account'
|
||||||
import accountEn from '@hcengineering/account/lang/en.json'
|
import accountEn from '@hcengineering/account/lang/en.json'
|
||||||
import accountRu from '@hcengineering/account/lang/ru.json'
|
import accountRu from '@hcengineering/account/lang/ru.json'
|
||||||
import { registerProviders } from '@hcengineering/auth-providers'
|
import { registerProviders } from '@hcengineering/auth-providers'
|
||||||
import { type MeasureContext } from '@hcengineering/core'
|
import { type Data, type MeasureContext, type Tx, type Version } from '@hcengineering/core'
|
||||||
|
import { getModelVersion, type MigrateOperation } from '@hcengineering/model-all'
|
||||||
import platform, { Severity, Status, addStringsLoader, setMetadata } from '@hcengineering/platform'
|
import platform, { Severity, Status, addStringsLoader, setMetadata } from '@hcengineering/platform'
|
||||||
import serverToken from '@hcengineering/server-token'
|
import serverToken from '@hcengineering/server-token'
|
||||||
import toolPlugin from '@hcengineering/server-tool'
|
import toolPlugin from '@hcengineering/server-tool'
|
||||||
@ -32,7 +39,14 @@ import { MongoClient } from 'mongodb'
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function serveAccount (measureCtx: MeasureContext, methods: Record<string, AccountMethod>, productId = ''): void {
|
export function serveAccount (
|
||||||
|
measureCtx: MeasureContext,
|
||||||
|
version: Data<Version>,
|
||||||
|
txes: Tx[],
|
||||||
|
migrateOperations: [string, MigrateOperation][],
|
||||||
|
productId: string = ''
|
||||||
|
): void {
|
||||||
|
const methods = getMethods(getModelVersion(), txes, migrateOperations)
|
||||||
const ACCOUNT_PORT = parseInt(process.env.ACCOUNT_PORT ?? '3000')
|
const ACCOUNT_PORT = parseInt(process.env.ACCOUNT_PORT ?? '3000')
|
||||||
const dbUri = process.env.MONGO_URL
|
const dbUri = process.env.MONGO_URL
|
||||||
if (dbUri === undefined) {
|
if (dbUri === undefined) {
|
||||||
@ -90,12 +104,21 @@ export function serveAccount (measureCtx: MeasureContext, methods: Record<string
|
|||||||
const app = new Koa()
|
const app = new Koa()
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
void client.then((p: MongoClient) => {
|
void client.then(async (p: MongoClient) => {
|
||||||
const db = p.db(ACCOUNT_DB)
|
const db = p.db(ACCOUNT_DB)
|
||||||
registerProviders(measureCtx, app, router, db, productId, serverSecret, frontURL)
|
registerProviders(measureCtx, app, router, db, productId, serverSecret, frontURL)
|
||||||
|
|
||||||
// We need to clean workspace with creating === true, since server is restarted.
|
// We need to clean workspace with creating === true, since server is restarted.
|
||||||
void cleanInProgressWorkspaces(db, productId)
|
void cleanInProgressWorkspaces(db, productId)
|
||||||
|
|
||||||
|
const worker = new UpgradeWorker(db, p, version, txes, migrateOperations, productId)
|
||||||
|
await worker.upgradeAll(measureCtx, {
|
||||||
|
errorHandler: async (ws, err) => {},
|
||||||
|
force: false,
|
||||||
|
console: false,
|
||||||
|
logs: 'upgrade-logs',
|
||||||
|
parallel: parseInt(process.env.PARALLEL ?? '1')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const extractToken = (header: IncomingHttpHeaders): string | undefined => {
|
const extractToken = (header: IncomingHttpHeaders): string | undefined => {
|
||||||
|
@ -66,7 +66,6 @@ import notification, {
|
|||||||
} from '@hcengineering/notification'
|
} from '@hcengineering/notification'
|
||||||
import { getMetadata, getResource, translate } from '@hcengineering/platform'
|
import { getMetadata, getResource, translate } from '@hcengineering/platform'
|
||||||
import type { TriggerControl } from '@hcengineering/server-core'
|
import type { TriggerControl } from '@hcengineering/server-core'
|
||||||
import { stripTags } from '@hcengineering/text'
|
|
||||||
import serverCore from '@hcengineering/server-core'
|
import serverCore from '@hcengineering/server-core'
|
||||||
import serverNotification, {
|
import serverNotification, {
|
||||||
getEmployee,
|
getEmployee,
|
||||||
@ -74,6 +73,7 @@ import serverNotification, {
|
|||||||
getPersonAccountById,
|
getPersonAccountById,
|
||||||
NOTIFICATION_BODY_SIZE
|
NOTIFICATION_BODY_SIZE
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
|
import { stripTags } from '@hcengineering/text'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
import webpush, { WebPushError } from 'web-push'
|
import webpush, { WebPushError } from 'web-push'
|
||||||
import { Content, NotifyResult } from './types'
|
import { Content, NotifyResult } from './types'
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import builder, { migrateOperations, getModelVersion } from '@hcengineering/model-all'
|
import builder, { migrateOperations, getModelVersion } from '@hcengineering/model-all'
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { Db, MongoClient } from 'mongodb'
|
import { Db, MongoClient } from 'mongodb'
|
||||||
import accountPlugin, { getAccount, getMethods, getWorkspaceByUrl } from '..'
|
import accountPlugin, { getAccount, getMethods, getWorkspaceByUrl } from '../operations'
|
||||||
import { setMetadata } from '@hcengineering/platform'
|
import { setMetadata } from '@hcengineering/platform'
|
||||||
import { MeasureMetricsContext } from '@hcengineering/core'
|
import { MeasureMetricsContext } from '@hcengineering/core'
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
1975
server/account/src/operations.ts
Normal file
1975
server/account/src/operations.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ export const accountId = 'account' as Plugin
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
const accountPlugin = plugin(accountId, {
|
export const accountPlugin = plugin(accountId, {
|
||||||
metadata: {
|
metadata: {
|
||||||
FrontURL: '' as Metadata<string>,
|
FrontURL: '' as Metadata<string>,
|
||||||
SES_URL: '' as Metadata<string>,
|
SES_URL: '' as Metadata<string>,
|
||||||
@ -26,5 +26,3 @@ const accountPlugin = plugin(accountId, {
|
|||||||
InviteSubject: '' as IntlString
|
InviteSubject: '' as IntlString
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default accountPlugin
|
|
||||||
|
160
server/account/src/service.ts
Normal file
160
server/account/src/service.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { BaseWorkspaceInfo, Data, RateLimiter, Tx, Version, type MeasureContext } from '@hcengineering/core'
|
||||||
|
import { MigrateOperation, ModelLogger } from '@hcengineering/model'
|
||||||
|
import { FileModelLogger } from '@hcengineering/server-tool'
|
||||||
|
import { Db, MongoClient } from 'mongodb'
|
||||||
|
import path from 'path'
|
||||||
|
import { listWorkspacesRaw, updateWorkspace, upgradeWorkspace, Workspace, WorkspaceInfo } from './operations'
|
||||||
|
|
||||||
|
export type UpgradeErrorHandler = (workspace: BaseWorkspaceInfo, error: any) => Promise<void>
|
||||||
|
|
||||||
|
export interface UpgradeOptions {
|
||||||
|
errorHandler: (workspace: BaseWorkspaceInfo, error: any) => Promise<void>
|
||||||
|
force: boolean
|
||||||
|
console: boolean
|
||||||
|
logs: string
|
||||||
|
parallel: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpgradeWorker {
|
||||||
|
constructor (
|
||||||
|
readonly db: Db,
|
||||||
|
readonly client: MongoClient,
|
||||||
|
readonly version: Data<Version>,
|
||||||
|
readonly txes: Tx[],
|
||||||
|
readonly migrationOperation: [string, MigrateOperation][],
|
||||||
|
readonly productId: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
canceled = false
|
||||||
|
|
||||||
|
st: number = Date.now()
|
||||||
|
workspaces: BaseWorkspaceInfo[] = []
|
||||||
|
toProcess: number = 0
|
||||||
|
|
||||||
|
async close (): Promise<void> {
|
||||||
|
this.canceled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _upgradeWorkspace (ctx: MeasureContext, ws: WorkspaceInfo, opt: UpgradeOptions): Promise<void> {
|
||||||
|
if (ws.disabled === true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const t = Date.now()
|
||||||
|
|
||||||
|
const ctxModelLogger: ModelLogger = {
|
||||||
|
log (msg: string, data: any): void {
|
||||||
|
void ctx.info(msg, data)
|
||||||
|
},
|
||||||
|
error (msg: string, data: any): void {
|
||||||
|
void ctx.error(msg, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = opt.console ? ctxModelLogger : new FileModelLogger(path.join(opt.logs, `${ws.workspace}.log`))
|
||||||
|
|
||||||
|
const avgTime = (Date.now() - this.st) / (this.workspaces.length - this.toProcess + 1)
|
||||||
|
await ctx.info('----------------------------------------------------------\n---UPGRADING----', {
|
||||||
|
pending: this.toProcess,
|
||||||
|
eta: Math.floor(avgTime * this.toProcess),
|
||||||
|
workspace: ws.workspace
|
||||||
|
})
|
||||||
|
this.toProcess--
|
||||||
|
try {
|
||||||
|
await upgradeWorkspace(
|
||||||
|
ctx,
|
||||||
|
this.version,
|
||||||
|
this.txes,
|
||||||
|
this.migrationOperation,
|
||||||
|
this.productId,
|
||||||
|
this.db,
|
||||||
|
ws.workspaceUrl ?? ws.workspace,
|
||||||
|
logger,
|
||||||
|
opt.force
|
||||||
|
)
|
||||||
|
await ctx.info('---done---------', {
|
||||||
|
pending: this.toProcess,
|
||||||
|
time: Date.now() - t,
|
||||||
|
workspace: ws.workspace
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
await opt.errorHandler(ws, err)
|
||||||
|
|
||||||
|
logger.log('error', err)
|
||||||
|
|
||||||
|
if (!opt.console) {
|
||||||
|
await ctx.error('error', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.info('---failed---------', {
|
||||||
|
pending: this.toProcess,
|
||||||
|
time: Date.now() - t,
|
||||||
|
workspace: ws.workspace
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
if (!opt.console) {
|
||||||
|
;(logger as FileModelLogger).close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upgradeAll (ctx: MeasureContext, opt: UpgradeOptions): Promise<void> {
|
||||||
|
const workspaces = await listWorkspacesRaw(this.db, this.productId)
|
||||||
|
workspaces.sort((a, b) => b.lastVisit - a.lastVisit)
|
||||||
|
|
||||||
|
// We need to update workspaces with missing workspaceUrl
|
||||||
|
for (const ws of workspaces) {
|
||||||
|
if (ws.workspaceUrl == null) {
|
||||||
|
const upd: Partial<Workspace> = {
|
||||||
|
workspaceUrl: ws.workspace
|
||||||
|
}
|
||||||
|
if (ws.workspaceName == null) {
|
||||||
|
upd.workspaceName = ws.workspace
|
||||||
|
}
|
||||||
|
await updateWorkspace(this.db, this.productId, ws, upd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const withError: string[] = []
|
||||||
|
this.toProcess = workspaces.length
|
||||||
|
this.st = Date.now()
|
||||||
|
|
||||||
|
if (opt.parallel !== 0) {
|
||||||
|
const parallel = opt.parallel
|
||||||
|
const rateLimit = new RateLimiter(parallel)
|
||||||
|
await ctx.info('parallel upgrade', { parallel })
|
||||||
|
await Promise.all(
|
||||||
|
workspaces.map((it) =>
|
||||||
|
rateLimit.add(async () => {
|
||||||
|
await ctx.with('do-upgrade', {}, async () => {
|
||||||
|
await this._upgradeWorkspace(ctx, it, opt)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await ctx.info('Upgrade done')
|
||||||
|
} else {
|
||||||
|
await ctx.info('UPGRADE write logs at:', { logs: opt.logs })
|
||||||
|
for (const ws of workspaces) {
|
||||||
|
await this._upgradeWorkspace(ctx, ws, opt)
|
||||||
|
}
|
||||||
|
if (withError.length > 0) {
|
||||||
|
await ctx.info('Failed workspaces', withError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user