mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
UBERF-6180: Fix account issues (#5063)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
352e8a4f3f
commit
5716c053fd
@ -30,8 +30,11 @@ export default async () => {
|
||||
for (const op of migrateOperations) {
|
||||
console.log('Migrate', op[0])
|
||||
await op[1].upgrade(client, {
|
||||
log (...data) {
|
||||
console.log(...data)
|
||||
log (msg, data) {
|
||||
console.log(msg, data)
|
||||
},
|
||||
error (msg, data) {
|
||||
console.error(msg, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
83
dev/tool/src/cleanOrphan.ts
Normal file
83
dev/tool/src/cleanOrphan.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { dropWorkspace, setWorkspaceDisabled, type Workspace } from '@hcengineering/account'
|
||||
import core, { AccountRole, MeasureMetricsContext, SortingOrder } from '@hcengineering/core'
|
||||
import contact from '@hcengineering/model-contact'
|
||||
import { getWorkspaceDB } from '@hcengineering/mongo'
|
||||
import { type StorageAdapter } from '@hcengineering/server-core'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import { type Db, type MongoClient } from 'mongodb'
|
||||
|
||||
export async function checkOrphanWorkspaces (
|
||||
workspaces: Workspace[],
|
||||
transactorUrl: string,
|
||||
productId: string,
|
||||
cmd: { remove: boolean, disable: boolean },
|
||||
db: Db,
|
||||
client: MongoClient,
|
||||
storageAdapter: StorageAdapter,
|
||||
excludes: string[]
|
||||
): Promise<void> {
|
||||
for (const ws of workspaces) {
|
||||
if (excludes.includes(ws.workspace) || (ws.workspaceUrl != null && excludes.includes(ws.workspaceUrl))) {
|
||||
continue
|
||||
}
|
||||
if ((ws.accounts ?? []).length === 0) {
|
||||
// Potential orhpan workspace
|
||||
// Let's connect and check activity.
|
||||
const connection = await connect(transactorUrl, { name: ws.workspace, productId }, undefined, { admin: 'true' })
|
||||
|
||||
const accounts = await connection.findAll(contact.class.PersonAccount, {})
|
||||
const employees = await connection.findAll(contact.mixin.Employee, {})
|
||||
let activeOwners = 0
|
||||
for (const person of employees) {
|
||||
const account = accounts.find((it) => it.person === person._id)
|
||||
if (account !== undefined) {
|
||||
if (account.role === AccountRole.Owner && person.active) {
|
||||
activeOwners++
|
||||
}
|
||||
// console.log('-----------', person.name, person.active, account.email, account.role)
|
||||
}
|
||||
}
|
||||
|
||||
// Find last transaction index:
|
||||
const wspace = { name: ws.workspace, productId }
|
||||
const hasBucket = await storageAdapter.exists(wspace)
|
||||
const [lastTx] = await connection.findAll(
|
||||
core.class.Tx,
|
||||
{
|
||||
objectSpace: { $ne: core.space.Model },
|
||||
createdBy: { $nin: [core.account.System, core.account.ConfigUser] },
|
||||
modifiedBy: { $ne: core.account.System }
|
||||
},
|
||||
{ limit: 1, sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
|
||||
await connection.close()
|
||||
const lastTxHours = Math.floor((Date.now() - (lastTx?.modifiedOn ?? 0)) / 1000 / 60 / 60)
|
||||
if (((activeOwners === 0 || lastTx == null) && lastTxHours > 1000) || !hasBucket) {
|
||||
const createdOn = (ws.createdOn ?? 0) !== 0 ? new Date(ws.createdOn).toDateString() : ''
|
||||
console.log(
|
||||
'Found orhpan workspace',
|
||||
`'${ws.workspaceName}' id: '${ws.workspace}' url:${ws.workspaceUrl} by: ${ws.createdBy ?? ''} on: '${createdOn}'`,
|
||||
lastTxHours + ' hours without modifications',
|
||||
hasBucket
|
||||
)
|
||||
if (cmd.disable) {
|
||||
await setWorkspaceDisabled(db, ws._id, true)
|
||||
}
|
||||
if (cmd.remove) {
|
||||
await dropWorkspace(new MeasureMetricsContext('tool', {}), db, productId, ws.workspace)
|
||||
const workspaceDb = getWorkspaceDB(client, { name: ws.workspace, productId })
|
||||
await workspaceDb.dropDatabase()
|
||||
if (storageAdapter !== undefined && hasBucket) {
|
||||
const docs = await storageAdapter.list(wspace)
|
||||
await storageAdapter.remove(
|
||||
wspace,
|
||||
docs.map((it) => it.name)
|
||||
)
|
||||
await storageAdapter?.delete(wspace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ import {
|
||||
getWorkspaceById,
|
||||
listAccounts,
|
||||
listWorkspaces,
|
||||
listWorkspacesPure,
|
||||
listWorkspacesRaw,
|
||||
replacePassword,
|
||||
setAccountAdmin,
|
||||
@ -51,7 +52,15 @@ import { MongoClient, type Db } from 'mongodb'
|
||||
import { clearTelegramHistory } from './telegram'
|
||||
import { diffWorkspace, updateField } from './workspace'
|
||||
|
||||
import { RateLimiter, getWorkspaceId, type AccountRole, type Data, type Tx, type Version } from '@hcengineering/core'
|
||||
import {
|
||||
getWorkspaceId,
|
||||
MeasureMetricsContext,
|
||||
RateLimiter,
|
||||
type AccountRole,
|
||||
type Data,
|
||||
type Tx,
|
||||
type Version
|
||||
} from '@hcengineering/core'
|
||||
import { consoleModelLogger, type MigrateOperation } from '@hcengineering/model'
|
||||
import { openAIConfigDefaults } from '@hcengineering/openai'
|
||||
import { type StorageAdapter } from '@hcengineering/server-core'
|
||||
@ -66,6 +75,7 @@ import {
|
||||
fixSkills,
|
||||
optimizeModel
|
||||
} from './clean'
|
||||
import { checkOrphanWorkspaces } from './cleanOrphan'
|
||||
import { changeConfiguration } from './configuration'
|
||||
import { fixMixinForeignAttributes, showMixinForeignAttributes } from './mixin'
|
||||
import { openAIConfig } from './openai'
|
||||
@ -84,6 +94,7 @@ export function devTool (
|
||||
productId: string,
|
||||
extendProgram?: (prog: Command) => void
|
||||
): void {
|
||||
const toolCtx = new MeasureMetricsContext('tool', {})
|
||||
const serverSecret = process.env.SERVER_SECRET
|
||||
if (serverSecret === undefined) {
|
||||
console.error('please provide server secret')
|
||||
@ -135,7 +146,7 @@ export function devTool (
|
||||
const { mongodbUri } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db) => {
|
||||
console.log(`creating account ${cmd.first as string} ${cmd.last as string} (${email})...`)
|
||||
await createAcc(db, productId, email, cmd.password, cmd.first, cmd.last, true)
|
||||
await createAcc(toolCtx, db, productId, email, cmd.password, cmd.first, cmd.last, true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -164,7 +175,7 @@ export function devTool (
|
||||
}
|
||||
console.log('assigning to workspace', workspaceInfo)
|
||||
try {
|
||||
await assignWorkspace(db, productId, email, workspaceInfo.workspace)
|
||||
await assignWorkspace(toolCtx, db, productId, email, workspaceInfo.workspace)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
}
|
||||
@ -215,6 +226,7 @@ export function devTool (
|
||||
const { mongodbUri, txes, version, migrateOperations } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db) => {
|
||||
const { client } = await createWorkspace(
|
||||
toolCtx,
|
||||
version,
|
||||
txes,
|
||||
migrateOperations,
|
||||
@ -292,6 +304,8 @@ export function devTool (
|
||||
}
|
||||
|
||||
const withError: string[] = []
|
||||
let toProcess = workspaces.length
|
||||
const st = Date.now()
|
||||
|
||||
async function _upgradeWorkspace (ws: WorkspaceInfo): Promise<void> {
|
||||
if (ws.disabled === true) {
|
||||
@ -301,7 +315,18 @@ export function devTool (
|
||||
const logger = cmd.console
|
||||
? consoleModelLogger
|
||||
: new FileModelLogger(path.join(cmd.logs, `${ws.workspace}.log`))
|
||||
console.log('---UPGRADING----', ws.workspace, !cmd.console ? (logger as FileModelLogger).file : '')
|
||||
|
||||
const avgTime = (Date.now() - st) / (workspaces.length - toProcess + 1)
|
||||
console.log(
|
||||
'---UPGRADING----',
|
||||
ws.workspace,
|
||||
!cmd.console ? (logger as FileModelLogger).file : '',
|
||||
'pending: ',
|
||||
toProcess,
|
||||
'ETA:',
|
||||
avgTime * toProcess
|
||||
)
|
||||
toProcess--
|
||||
try {
|
||||
await upgradeWorkspace(
|
||||
version,
|
||||
@ -347,6 +372,33 @@ export function devTool (
|
||||
})
|
||||
})
|
||||
|
||||
program
|
||||
.command('remove-unused-workspaces')
|
||||
.description(
|
||||
'remove unused workspaces, please pass --remove to really delete them. Without it will only mark them disabled'
|
||||
)
|
||||
.option('-r|--remove [remove]', 'Force remove', false)
|
||||
.option('-d|--disable [disable]', 'Force disable', false)
|
||||
.option('-e|--exclude [exclude]', 'A comma separated list of workspaces to exclude', '')
|
||||
.action(async (cmd: { remove: boolean, disable: boolean, exclude: string }) => {
|
||||
const { mongodbUri, storageAdapter } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db, client) => {
|
||||
const workspaces = await listWorkspacesPure(db, productId)
|
||||
|
||||
// We need to update workspaces with missing workspaceUrl
|
||||
await checkOrphanWorkspaces(
|
||||
workspaces,
|
||||
transactorUrl,
|
||||
productId,
|
||||
cmd,
|
||||
db,
|
||||
client,
|
||||
storageAdapter,
|
||||
cmd.exclude.split(',')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
program
|
||||
.command('drop-workspace <name>')
|
||||
.description('drop workspace')
|
||||
@ -358,7 +410,7 @@ export function devTool (
|
||||
console.log('no workspace exists')
|
||||
return
|
||||
}
|
||||
await dropWorkspace(db, productId, workspace)
|
||||
await dropWorkspace(toolCtx, db, productId, workspace)
|
||||
})
|
||||
})
|
||||
|
||||
@ -368,7 +420,7 @@ export function devTool (
|
||||
.action(async () => {
|
||||
const { mongodbUri, version } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db) => {
|
||||
const workspacesJSON = JSON.stringify(await listWorkspaces(db, productId), null, 2)
|
||||
const workspacesJSON = JSON.stringify(await listWorkspaces(toolCtx, db, productId), null, 2)
|
||||
console.info(workspacesJSON)
|
||||
|
||||
console.log('latest model version:', JSON.stringify(version))
|
||||
@ -392,7 +444,7 @@ export function devTool (
|
||||
.action(async (email: string, cmd) => {
|
||||
const { mongodbUri } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db) => {
|
||||
await dropAccount(db, productId, email)
|
||||
await dropAccount(toolCtx, db, productId, email)
|
||||
})
|
||||
})
|
||||
|
||||
@ -434,26 +486,24 @@ export function devTool (
|
||||
.description('dump workspace transactions and minio resources')
|
||||
.action(async (bucketName: string, dirName: string, workspace: string, cmd) => {
|
||||
const { storageAdapter } = prepareTools()
|
||||
const wsId = getWorkspaceId(workspace, productId)
|
||||
const storage = await createStorageBackupStorage(storageAdapter, wsId, dirName)
|
||||
await backup(transactorUrl, wsId, storage)
|
||||
const storage = await createStorageBackupStorage(storageAdapter, getWorkspaceId(bucketName, productId), dirName)
|
||||
await backup(transactorUrl, getWorkspaceId(workspace, productId), storage)
|
||||
})
|
||||
program
|
||||
.command('backup-s3-restore <bucketName>, <dirName> <workspace> [date]')
|
||||
.command('backup-s3-restore <bucketName> <dirName> <workspace> [date]')
|
||||
.description('dump workspace transactions and minio resources')
|
||||
.action(async (bucketName: string, dirName: string, workspace: string, date, cmd) => {
|
||||
const { storageAdapter } = prepareTools()
|
||||
const wsId = getWorkspaceId(bucketName, productId)
|
||||
const storage = await createStorageBackupStorage(storageAdapter, wsId, dirName)
|
||||
await restore(transactorUrl, wsId, storage, parseInt(date ?? '-1'))
|
||||
const storage = await createStorageBackupStorage(storageAdapter, getWorkspaceId(bucketName), dirName)
|
||||
await restore(transactorUrl, getWorkspaceId(workspace, productId), storage, parseInt(date ?? '-1'))
|
||||
})
|
||||
program
|
||||
.command('backup-s3-list <bucketName> <dirName>')
|
||||
.description('list snaphost ids for backup')
|
||||
.action(async (bucketName: string, dirName: string, cmd) => {
|
||||
const { storageAdapter } = prepareTools()
|
||||
const wsId = getWorkspaceId(bucketName, productId)
|
||||
const storage = await createStorageBackupStorage(storageAdapter, wsId, dirName)
|
||||
|
||||
const storage = await createStorageBackupStorage(storageAdapter, getWorkspaceId(bucketName, productId), dirName)
|
||||
await backupList(storage)
|
||||
})
|
||||
|
||||
@ -510,7 +560,7 @@ export function devTool (
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const workspaces = await listWorkspaces(db, productId)
|
||||
const workspaces = await listWorkspaces(toolCtx, db, productId)
|
||||
|
||||
for (const w of workspaces) {
|
||||
console.log(`clearing ${w.workspace} history:`)
|
||||
|
@ -56,14 +56,18 @@ export async function createOrUpdate<T extends Doc> (
|
||||
* @public
|
||||
*/
|
||||
export interface ModelLogger {
|
||||
log: (...data: any[]) => void
|
||||
log: (msg: string, data: any) => void
|
||||
error: (msg: string, err: any) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const consoleModelLogger: ModelLogger = {
|
||||
log (...data: any[]): void {
|
||||
console.log(...data)
|
||||
log (msg: string, data: any): void {
|
||||
console.log(msg, data)
|
||||
},
|
||||
error (msg: string, data: any): void {
|
||||
console.error(msg, data)
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +206,12 @@ export class LiveQuery implements WithTx, Client {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options?.limit === 1 && options.total !== true) {
|
||||
if (
|
||||
options?.limit === 1 &&
|
||||
options.total !== true &&
|
||||
options?.sort === undefined &&
|
||||
options?.projection === undefined
|
||||
) {
|
||||
const docs = this.documentRefs.get(classKey)
|
||||
if (docs !== undefined) {
|
||||
const _docs = Array.from(docs.values()).map((it) => it.doc)
|
||||
|
@ -619,7 +619,7 @@
|
||||
$: modern = currentApplication?.modern ?? false
|
||||
</script>
|
||||
|
||||
{#if employee && !employee.active}
|
||||
{#if employee && !employee.active && !isAdminUser()}
|
||||
<div class="flex-col-center justify-center h-full flex-grow">
|
||||
<h1><Label label={workbench.string.AccountDisabled} /></h1>
|
||||
<Label label={workbench.string.AccountDisabledDescr} />
|
||||
|
@ -14,8 +14,8 @@
|
||||
//
|
||||
|
||||
import { getMethods } from '@hcengineering/account'
|
||||
import { type Tx } from '@hcengineering/core'
|
||||
import builder, { migrateOperations, getModelVersion } from '@hcengineering/model-all'
|
||||
import { MeasureMetricsContext, newMetrics, type Tx } from '@hcengineering/core'
|
||||
import builder, { getModelVersion, migrateOperations } from '@hcengineering/model-all'
|
||||
import { serveAccount } from '.'
|
||||
|
||||
const enabled = (process.env.MODEL_ENABLED ?? '*').split(',').map((it) => it.trim())
|
||||
@ -23,4 +23,6 @@ const disabled = (process.env.MODEL_DISABLED ?? '').split(',').map((it) => it.tr
|
||||
|
||||
const txes = JSON.parse(JSON.stringify(builder(enabled, disabled).getTxes())) as Tx[]
|
||||
|
||||
serveAccount(getMethods(getModelVersion(), txes, migrateOperations))
|
||||
const metricsContext = new MeasureMetricsContext('account', {}, {}, newMetrics())
|
||||
|
||||
serveAccount(metricsContext, getMethods(getModelVersion(), txes, migrateOperations))
|
||||
|
@ -17,6 +17,8 @@
|
||||
import account, { ACCOUNT_DB, type AccountMethod, accountId } from '@hcengineering/account'
|
||||
import accountEn from '@hcengineering/account/lang/en.json'
|
||||
import accountRu from '@hcengineering/account/lang/ru.json'
|
||||
import { registerProviders } from '@hcengineering/auth-providers'
|
||||
import { type MeasureContext } from '@hcengineering/core'
|
||||
import platform, { Severity, Status, addStringsLoader, setMetadata } from '@hcengineering/platform'
|
||||
import serverToken from '@hcengineering/server-token'
|
||||
import toolPlugin from '@hcengineering/server-tool'
|
||||
@ -26,12 +28,11 @@ import Koa from 'koa'
|
||||
import bodyParser from 'koa-bodyparser'
|
||||
import Router from 'koa-router'
|
||||
import { MongoClient } from 'mongodb'
|
||||
import { registerProviders } from '@hcengineering/auth-providers'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function serveAccount (methods: Record<string, AccountMethod>, productId = ''): void {
|
||||
export function serveAccount (measureCtx: MeasureContext, methods: Record<string, AccountMethod>, productId = ''): void {
|
||||
const ACCOUNT_PORT = parseInt(process.env.ACCOUNT_PORT ?? '3000')
|
||||
const dbUri = process.env.MONGO_URL
|
||||
if (dbUri === undefined) {
|
||||
@ -89,9 +90,9 @@ export function serveAccount (methods: Record<string, AccountMethod>, productId
|
||||
const app = new Koa()
|
||||
const router = new Router()
|
||||
|
||||
void client.then((p) => {
|
||||
void client.then((p: MongoClient) => {
|
||||
const db = p.db(ACCOUNT_DB)
|
||||
registerProviders(app, router, db, productId, serverSecret, frontURL)
|
||||
registerProviders(measureCtx, app, router, db, productId, serverSecret, frontURL)
|
||||
})
|
||||
|
||||
const extractToken = (header: IncomingHttpHeaders): string | undefined => {
|
||||
@ -120,7 +121,7 @@ export function serveAccount (methods: Record<string, AccountMethod>, productId
|
||||
client = await client
|
||||
}
|
||||
const db = client.db(ACCOUNT_DB)
|
||||
const result = await method(db, productId, request, token)
|
||||
const result = await method(measureCtx, db, productId, request, token)
|
||||
ctx.body = result
|
||||
})
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { concatLink } from '@hcengineering/core'
|
||||
import Router from 'koa-router'
|
||||
import { Strategy as GitHubStrategy } from 'passport-github2'
|
||||
import { joinWithProvider, loginWithProvider } from '@hcengineering/account'
|
||||
import { concatLink, MeasureContext } from '@hcengineering/core'
|
||||
import Router from 'koa-router'
|
||||
import { Db } from 'mongodb'
|
||||
import { Strategy as GitHubStrategy } from 'passport-github2'
|
||||
import { Passport } from '.'
|
||||
|
||||
export function registerGithub (
|
||||
ctx: MeasureContext,
|
||||
passport: Passport,
|
||||
router: Router<any, any>,
|
||||
accountsUrl: string,
|
||||
@ -45,14 +46,16 @@ export function registerGithub (
|
||||
const [first, last] = ctx.state.user.displayName.split(' ')
|
||||
if (email !== undefined) {
|
||||
if (ctx.query?.state != null) {
|
||||
const loginInfo = await joinWithProvider(db, productId, email, first, last, ctx.query.state, {
|
||||
const loginInfo = await joinWithProvider(ctx, db, productId, email, first, last, ctx.query.state, {
|
||||
githubId: ctx.state.user.id
|
||||
})
|
||||
if (ctx.session != null) {
|
||||
ctx.session.loginInfo = loginInfo
|
||||
}
|
||||
} else {
|
||||
const loginInfo = await loginWithProvider(db, productId, email, first, last, { githubId: ctx.state.user.id })
|
||||
const loginInfo = await loginWithProvider(ctx, db, productId, email, first, last, {
|
||||
githubId: ctx.state.user.id
|
||||
})
|
||||
if (ctx.session != null) {
|
||||
ctx.session.loginInfo = loginInfo
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { concatLink } from '@hcengineering/core'
|
||||
import Router from 'koa-router'
|
||||
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
|
||||
import { joinWithProvider, loginWithProvider } from '@hcengineering/account'
|
||||
import { concatLink, MeasureContext } from '@hcengineering/core'
|
||||
import Router from 'koa-router'
|
||||
import { Db } from 'mongodb'
|
||||
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
|
||||
import { Passport } from '.'
|
||||
|
||||
export function registerGoogle (
|
||||
ctx: MeasureContext,
|
||||
passport: Passport,
|
||||
router: Router<any, any>,
|
||||
accountsUrl: string,
|
||||
@ -46,12 +47,12 @@ export function registerGoogle (
|
||||
const last = ctx.state.user.name.familyName
|
||||
if (email !== undefined) {
|
||||
if (ctx.query?.state != null) {
|
||||
const loginInfo = await joinWithProvider(db, productId, email, first, last, ctx.query.state)
|
||||
const loginInfo = await joinWithProvider(ctx, db, productId, email, first, last, ctx.query.state)
|
||||
if (ctx.session != null) {
|
||||
ctx.session.loginInfo = loginInfo
|
||||
}
|
||||
} else {
|
||||
const loginInfo = await loginWithProvider(db, productId, email, first, last)
|
||||
const loginInfo = await loginWithProvider(ctx, db, productId, email, first, last)
|
||||
if (ctx.session != null) {
|
||||
ctx.session.loginInfo = loginInfo
|
||||
}
|
||||
|
@ -5,10 +5,12 @@ import session from 'koa-session'
|
||||
import { Db } from 'mongodb'
|
||||
import { registerGithub } from './github'
|
||||
import { registerGoogle } from './google'
|
||||
import { MeasureContext } from '@hcengineering/core'
|
||||
|
||||
export type Passport = typeof passport
|
||||
|
||||
export type AuthProvider = (
|
||||
ctx: MeasureContext,
|
||||
passport: Passport,
|
||||
router: Router<any, any>,
|
||||
accountsUrl: string,
|
||||
@ -18,6 +20,7 @@ export type AuthProvider = (
|
||||
) => string | undefined
|
||||
|
||||
export function registerProviders (
|
||||
ctx: MeasureContext,
|
||||
app: Koa<Koa.DefaultState, Koa.DefaultContext>,
|
||||
router: Router<any, any>,
|
||||
db: Db,
|
||||
@ -57,7 +60,7 @@ export function registerProviders (
|
||||
const res: string[] = []
|
||||
const providers: AuthProvider[] = [registerGoogle, registerGithub]
|
||||
for (const provider of providers) {
|
||||
const value = provider(passport, router, accountsUrl, db, productId, frontUrl)
|
||||
const value = provider(ctx, passport, router, accountsUrl, db, productId, frontUrl)
|
||||
if (value !== undefined) res.push(value)
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,14 @@ import { randomBytes } from 'crypto'
|
||||
import { Db, MongoClient } from 'mongodb'
|
||||
import accountPlugin, { getAccount, getMethods, getWorkspaceByUrl } from '..'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import { MeasureMetricsContext } from '@hcengineering/core'
|
||||
|
||||
const DB_NAME = 'test_accounts'
|
||||
|
||||
const methods = getMethods(getModelVersion(), builder().getTxes(), migrateOperations)
|
||||
|
||||
const metricsContext = new MeasureMetricsContext('account', {})
|
||||
|
||||
describe('server', () => {
|
||||
const dbUri = process.env.MONGO_URL ?? 'mongodb://localhost:27017'
|
||||
let conn: MongoClient
|
||||
@ -48,7 +51,7 @@ describe('server', () => {
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
}
|
||||
|
||||
const result = await methods.createWorkspace(db, '', request)
|
||||
const result = await methods.createWorkspace(metricsContext, db, '', request)
|
||||
expect(result.result).toBeDefined()
|
||||
workspace = result.result as string
|
||||
})
|
||||
@ -59,12 +62,12 @@ describe('server', () => {
|
||||
params: ['andrey2', '123']
|
||||
}
|
||||
|
||||
const result = await methods.createAccount(db, '', request)
|
||||
const result = await methods.createAccount(metricsContext, db, '', request)
|
||||
expect(result.result).toBeDefined()
|
||||
})
|
||||
|
||||
it('should not create, duplicate account', async () => {
|
||||
await methods.createAccount(db, '', {
|
||||
await methods.createAccount(metricsContext, db, '', {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
@ -74,20 +77,20 @@ describe('server', () => {
|
||||
params: ['andrey', '123']
|
||||
}
|
||||
|
||||
const result = await methods.createAccount(db, '', request)
|
||||
const result = await methods.createAccount(metricsContext, db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
it('should login', async () => {
|
||||
await methods.createAccount(db, '', {
|
||||
await methods.createAccount(metricsContext, db, '', {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
await methods.createWorkspace(db, '', {
|
||||
await methods.createWorkspace(metricsContext, db, '', {
|
||||
method: 'createWorkspace',
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
})
|
||||
await methods.assignWorkspace(db, '', {
|
||||
await methods.assignWorkspace(metricsContext, db, '', {
|
||||
method: 'assignWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
@ -97,7 +100,7 @@ describe('server', () => {
|
||||
params: ['andrey', '123', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
expect(result.result).toBeDefined()
|
||||
})
|
||||
|
||||
@ -107,7 +110,7 @@ describe('server', () => {
|
||||
params: ['andrey', '123555', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
@ -117,7 +120,7 @@ describe('server', () => {
|
||||
params: ['andrey1', '123555', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
@ -127,20 +130,20 @@ describe('server', () => {
|
||||
params: ['andrey', '123', 'non-existent-workspace']
|
||||
}
|
||||
|
||||
const result = await methods.login(db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
it('do remove workspace', async () => {
|
||||
await methods.createAccount(db, '', {
|
||||
await methods.createAccount(metricsContext, db, '', {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
await methods.createWorkspace(db, '', {
|
||||
await methods.createWorkspace(metricsContext, db, '', {
|
||||
method: 'createWorkspace',
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
})
|
||||
await methods.assignWorkspace(db, '', {
|
||||
await methods.assignWorkspace(metricsContext, db, '', {
|
||||
method: 'assignWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
@ -149,7 +152,7 @@ describe('server', () => {
|
||||
expect((await getAccount(db, 'andrey'))?.workspaces.length).toEqual(1)
|
||||
expect((await getWorkspaceByUrl(db, '', workspace))?.accounts.length).toEqual(1)
|
||||
|
||||
await methods.removeWorkspace(db, '', {
|
||||
await methods.removeWorkspace(metricsContext, db, '', {
|
||||
method: 'removeWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
|
@ -30,6 +30,8 @@ import core, {
|
||||
Data,
|
||||
generateId,
|
||||
getWorkspaceId,
|
||||
MeasureContext,
|
||||
RateLimiter,
|
||||
Ref,
|
||||
systemAccountEmail,
|
||||
Tx,
|
||||
@ -108,6 +110,8 @@ export interface Workspace {
|
||||
workspaceName?: string // An displayed workspace name
|
||||
createdOn: number
|
||||
lastVisit: number
|
||||
|
||||
createdBy: string
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +231,7 @@ function toAccountInfo (account: Account): AccountInfo {
|
||||
return result
|
||||
}
|
||||
|
||||
async function getAccountInfo (db: Db, email: string, password: string): Promise<AccountInfo> {
|
||||
async function getAccountInfo (ctx: MeasureContext, db: Db, email: string, password: string): Promise<AccountInfo> {
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email }))
|
||||
@ -241,11 +245,17 @@ async function getAccountInfo (db: Db, email: string, password: string): Promise
|
||||
return toAccountInfo(account)
|
||||
}
|
||||
|
||||
async function getAccountInfoByToken (db: Db, productId: string, token: string): Promise<LoginInfo> {
|
||||
async function getAccountInfoByToken (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string
|
||||
): Promise<LoginInfo> {
|
||||
let email: string = ''
|
||||
try {
|
||||
email = decodeToken(token)?.email
|
||||
} catch (err: any) {
|
||||
await ctx.error('Invalid token', { token })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Unauthorized, {}))
|
||||
}
|
||||
const account = await getAccount(db, email)
|
||||
@ -270,17 +280,28 @@ async function getAccountInfoByToken (db: Db, productId: string, token: string):
|
||||
* @param workspace -
|
||||
* @returns
|
||||
*/
|
||||
export async function login (db: Db, productId: string, _email: string, password: string): Promise<LoginInfo> {
|
||||
export async function login (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
password: string
|
||||
): Promise<LoginInfo> {
|
||||
const email = cleanEmail(_email)
|
||||
console.log(`login attempt:${email}`)
|
||||
const info = await getAccountInfo(db, email, password)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
confirmed: info.confirmed ?? true,
|
||||
token: generateToken(email, getWorkspaceId('', productId), getExtra(info))
|
||||
try {
|
||||
const info = await getAccountInfo(ctx, db, email, password)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
confirmed: info.confirmed ?? true,
|
||||
token: generateToken(email, getWorkspaceId('', productId), getExtra(info))
|
||||
}
|
||||
await ctx.info('login success', { email, productId })
|
||||
return result
|
||||
} catch (err: any) {
|
||||
await ctx.error('login failed', { email, productId, _email, err })
|
||||
throw err
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,6 +320,7 @@ function getExtra (info: Account | AccountInfo | null, rec?: Record<string, any>
|
||||
* @public
|
||||
*/
|
||||
export async function selectWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
@ -328,6 +350,7 @@ export async function selectWorkspace (
|
||||
|
||||
if (workspaceInfo !== null) {
|
||||
if (workspaceInfo.disabled === true) {
|
||||
await ctx.error('workspace disabled', { workspaceUrl, email })
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspaceUrl })
|
||||
)
|
||||
@ -347,7 +370,7 @@ export async function selectWorkspace (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.error('workspace error', { workspaceUrl, email })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
|
||||
@ -386,6 +409,7 @@ export async function useInvite (db: Db, inviteId: ObjectId): Promise<void> {
|
||||
* @public
|
||||
*/
|
||||
export async function join (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -395,11 +419,11 @@ export async function join (
|
||||
const email = cleanEmail(_email)
|
||||
const invite = await getInvite(db, inviteId)
|
||||
const workspace = await checkInvite(invite, email)
|
||||
console.log(`join attempt:${email}, ${workspace.name}`)
|
||||
const ws = await assignWorkspace(db, productId, email, workspace.name)
|
||||
await ctx.info(`join attempt:${email}, ${workspace.name}`)
|
||||
const ws = await assignWorkspace(ctx, db, productId, email, workspace.name)
|
||||
|
||||
const token = (await login(db, productId, email, password)).token
|
||||
const result = await selectWorkspace(db, productId, token, ws.workspaceUrl ?? ws.workspace)
|
||||
const token = (await login(ctx, db, productId, email, password)).token
|
||||
const result = await selectWorkspace(ctx, db, productId, token, ws.workspaceUrl ?? ws.workspace)
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
@ -427,10 +451,11 @@ export async function confirmEmail (db: Db, _email: string): Promise<Account> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function confirm (db: Db, productId: string, token: string): Promise<LoginInfo> {
|
||||
export async function confirm (ctx: MeasureContext, db: Db, productId: string, token: string): Promise<LoginInfo> {
|
||||
const decode = decodeToken(token)
|
||||
const _email = decode.extra?.confirm
|
||||
if (_email === undefined) {
|
||||
await ctx.error('confirm email invalid', { token: decode })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: _email }))
|
||||
}
|
||||
const email = cleanEmail(_email)
|
||||
@ -441,6 +466,7 @@ export async function confirm (db: Db, productId: string, token: string): Promis
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId('', productId), getExtra(account))
|
||||
}
|
||||
await ctx.info('confirm success', { email, productId })
|
||||
return result
|
||||
}
|
||||
|
||||
@ -491,6 +517,7 @@ async function sendConfirmation (productId: string, account: Account): Promise<v
|
||||
* @public
|
||||
*/
|
||||
export async function signUpJoin (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -505,6 +532,7 @@ export async function signUpJoin (
|
||||
const workspace = await checkInvite(invite, email)
|
||||
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
|
||||
await createAcc(
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
email,
|
||||
@ -513,10 +541,10 @@ export async function signUpJoin (
|
||||
last,
|
||||
invite?.emailMask === email || sesURL === undefined || sesURL === ''
|
||||
)
|
||||
const ws = await assignWorkspace(db, productId, email, workspace.name)
|
||||
const ws = await assignWorkspace(ctx, db, productId, email, workspace.name)
|
||||
|
||||
const token = (await login(db, productId, email, password)).token
|
||||
const result = await selectWorkspace(db, productId, token, ws.workspaceUrl ?? ws.workspace)
|
||||
const token = (await login(ctx, db, productId, email, password)).token
|
||||
const result = await selectWorkspace(ctx, db, productId, token, ws.workspaceUrl ?? ws.workspace)
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
@ -525,6 +553,7 @@ export async function signUpJoin (
|
||||
* @public
|
||||
*/
|
||||
export async function createAcc (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -540,6 +569,7 @@ export async function createAcc (
|
||||
|
||||
const systemEmails = [systemAccountEmail]
|
||||
if (systemEmails.includes(email)) {
|
||||
await ctx.error('system email used for account', { email })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountAlreadyExists, { account: email }))
|
||||
}
|
||||
|
||||
@ -570,10 +600,11 @@ export async function createAcc (
|
||||
if (sesURL !== undefined && sesURL !== '') {
|
||||
await sendConfirmation(productId, newAccount)
|
||||
} else {
|
||||
console.info('Please provide email service url to enable email confirmations.')
|
||||
await ctx.info('Please provide email service url to enable email confirmations.')
|
||||
await confirmEmail(db, email)
|
||||
}
|
||||
}
|
||||
await ctx.info('account created', { account: email })
|
||||
return newAccount
|
||||
}
|
||||
|
||||
@ -581,6 +612,7 @@ export async function createAcc (
|
||||
* @public
|
||||
*/
|
||||
export async function createAccount (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -590,7 +622,16 @@ export async function createAccount (
|
||||
): Promise<LoginInfo> {
|
||||
const email = cleanEmail(_email)
|
||||
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
|
||||
const account = await createAcc(db, productId, email, password, first, last, sesURL === undefined || sesURL === '')
|
||||
const account = await createAcc(
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
email,
|
||||
password,
|
||||
first,
|
||||
last,
|
||||
sesURL === undefined || sesURL === ''
|
||||
)
|
||||
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
@ -603,7 +644,7 @@ export async function createAccount (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function listWorkspaces (db: Db, productId: string): Promise<WorkspaceInfo[]> {
|
||||
export async function listWorkspaces (ctx: MeasureContext, db: Db, productId: string): Promise<WorkspaceInfo[]> {
|
||||
return (await db.collection<Workspace>(WORKSPACE_COLLECTION).find(withProductId(productId, {})).toArray())
|
||||
.map((it) => ({ ...it, productId }))
|
||||
.filter((it) => it.disabled !== true)
|
||||
@ -619,6 +660,22 @@ export async function listWorkspacesRaw (db: Db, productId: string): Promise<Wor
|
||||
.filter((it) => it.disabled !== true)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function listWorkspacesPure (db: Db, productId: string): Promise<Workspace[]> {
|
||||
return (await db.collection<Workspace>(WORKSPACE_COLLECTION).find(withProductId(productId, {})).toArray()).map(
|
||||
(it) => ({ ...it, productId })
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function setWorkspaceDisabled (db: Db, workspaceId: Workspace['_id'], disabled: boolean): Promise<void> {
|
||||
await db.collection<Workspace>(WORKSPACE_COLLECTION).updateOne({ _id: workspaceId }, { $set: { disabled } })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -680,9 +737,10 @@ async function generateWorkspaceRecord (
|
||||
version,
|
||||
workspaceName,
|
||||
accounts: [],
|
||||
disabled: false,
|
||||
disabled: true,
|
||||
createdOn: Date.now(),
|
||||
lastVisit: Date.now()
|
||||
lastVisit: Date.now(),
|
||||
createdBy: email
|
||||
}
|
||||
// Add fixed workspace
|
||||
const id = await coll.insertOne(data)
|
||||
@ -709,9 +767,10 @@ async function generateWorkspaceRecord (
|
||||
version,
|
||||
workspaceName,
|
||||
accounts: [],
|
||||
disabled: false,
|
||||
disabled: true,
|
||||
createdOn: Date.now(),
|
||||
lastVisit: Date.now()
|
||||
lastVisit: Date.now(),
|
||||
createdBy: email
|
||||
}
|
||||
// Nice we do not have a workspace or workspaceUrl duplicated.
|
||||
const id = await coll.insertOne(data)
|
||||
@ -736,10 +795,13 @@ async function generateWorkspaceRecord (
|
||||
|
||||
let searchPromise: Promise<Workspace> | undefined
|
||||
|
||||
const rateLimiter = new RateLimiter(3)
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createWorkspace (
|
||||
ctx: MeasureContext,
|
||||
version: Data<Version>,
|
||||
txes: Tx[],
|
||||
migrationOperation: [string, MigrateOperation][],
|
||||
@ -749,33 +811,53 @@ export async function createWorkspace (
|
||||
workspaceName: string,
|
||||
workspace?: string
|
||||
): Promise<{ workspaceInfo: Workspace, err?: any, client?: Client }> {
|
||||
// We need to search for duplicate workspaceUrl
|
||||
await searchPromise
|
||||
return await rateLimiter.exec(async () => {
|
||||
// We need to search for duplicate workspaceUrl
|
||||
await searchPromise
|
||||
|
||||
// Safe generate workspace record.
|
||||
searchPromise = generateWorkspaceRecord(db, email, productId, version, workspaceName, workspace)
|
||||
// Safe generate workspace record.
|
||||
searchPromise = generateWorkspaceRecord(db, email, productId, version, workspaceName, workspace)
|
||||
|
||||
const workspaceInfo = await searchPromise
|
||||
let client: Client
|
||||
try {
|
||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const wsId = getWorkspaceId(workspaceInfo.workspace, productId)
|
||||
if (initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null) {
|
||||
client = await initModel(getTransactor(), wsId, txes, [])
|
||||
await client.close()
|
||||
await cloneWorkspace(
|
||||
getTransactor(),
|
||||
getWorkspaceId(initWS, productId),
|
||||
getWorkspaceId(workspaceInfo.workspace, productId)
|
||||
)
|
||||
client = await upgradeModel(getTransactor(), wsId, txes, migrationOperation)
|
||||
} else {
|
||||
client = await initModel(getTransactor(), wsId, txes, migrationOperation)
|
||||
const workspaceInfo = await searchPromise
|
||||
let client: Client
|
||||
const childLogger = ctx.newChild(
|
||||
'createWorkspace',
|
||||
{ workspace: workspaceInfo.workspace },
|
||||
{},
|
||||
ctx.logger.childLogger?.(workspaceInfo.workspace, {}) ?? ctx.logger
|
||||
)
|
||||
const ctxModellogger: ModelLogger = {
|
||||
log: (msg, data) => {
|
||||
void childLogger.info(msg, data)
|
||||
},
|
||||
error: (msg, data) => {
|
||||
void childLogger.error(msg, data)
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
return { workspaceInfo, err, client: {} as any }
|
||||
}
|
||||
return { workspaceInfo, client }
|
||||
try {
|
||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const wsId = getWorkspaceId(workspaceInfo.workspace, productId)
|
||||
if (initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null) {
|
||||
client = await initModel(getTransactor(), wsId, txes, [], ctxModellogger)
|
||||
await client.close()
|
||||
await cloneWorkspace(
|
||||
getTransactor(),
|
||||
getWorkspaceId(initWS, productId),
|
||||
getWorkspaceId(workspaceInfo.workspace, productId)
|
||||
)
|
||||
client = await upgradeModel(getTransactor(), wsId, txes, migrationOperation, ctxModellogger)
|
||||
} else {
|
||||
client = await initModel(getTransactor(), wsId, txes, migrationOperation, ctxModellogger)
|
||||
}
|
||||
} catch (err: any) {
|
||||
return { workspaceInfo, err, client: {} as any }
|
||||
}
|
||||
// Workspace is created, we need to clear disabled flag.
|
||||
await db
|
||||
.collection<Omit<Workspace, '_id'>>(WORKSPACE_COLLECTION)
|
||||
.updateOne({ _id: workspaceInfo._id }, { $set: { disabled: false } })
|
||||
return { workspaceInfo, client }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -828,10 +910,10 @@ export async function upgradeWorkspace (
|
||||
*/
|
||||
export const createUserWorkspace =
|
||||
(version: Data<Version>, txes: Tx[], migrationOperation: [string, MigrateOperation][]) =>
|
||||
async (db: Db, productId: string, token: string, workspaceName: string): Promise<LoginInfo> => {
|
||||
async (ctx: MeasureContext, db: Db, productId: string, token: string, workspaceName: string): Promise<LoginInfo> => {
|
||||
const { email } = decodeToken(token)
|
||||
|
||||
console.log(`Creating workspace for "${workspaceName}" for ${email}`)
|
||||
await ctx.info('Creating workspace', { workspaceName, email })
|
||||
|
||||
const info = await getAccount(db, email)
|
||||
|
||||
@ -851,6 +933,7 @@ export const createUserWorkspace =
|
||||
}
|
||||
|
||||
const { workspaceInfo, err, client } = await createWorkspace(
|
||||
ctx,
|
||||
version,
|
||||
txes,
|
||||
migrationOperation,
|
||||
@ -861,7 +944,7 @@ export const createUserWorkspace =
|
||||
)
|
||||
|
||||
if (err != null) {
|
||||
console.error(err)
|
||||
await ctx.error('failed to create workspace', { err, workspaceName, email })
|
||||
// We need to drop workspace, to prevent wrong data usage.
|
||||
|
||||
await db.collection(WORKSPACE_COLLECTION).updateOne(
|
||||
@ -880,7 +963,7 @@ export const createUserWorkspace =
|
||||
|
||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const shouldUpdateAccount = initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null
|
||||
await assignWorkspace(db, productId, email, workspaceInfo.workspace, shouldUpdateAccount, client)
|
||||
await assignWorkspace(ctx, db, productId, email, workspaceInfo.workspace, shouldUpdateAccount, client)
|
||||
await setRole(email, workspaceInfo.workspace, productId, AccountRole.Owner, client)
|
||||
} finally {
|
||||
await client?.close()
|
||||
@ -892,7 +975,7 @@ export const createUserWorkspace =
|
||||
productId,
|
||||
workspace: workspaceInfo.workspaceUrl
|
||||
}
|
||||
console.log(`Creating workspace "${workspaceName}" Done`)
|
||||
await ctx.info('Creating workspace done', { workspaceName, email })
|
||||
return result
|
||||
}
|
||||
|
||||
@ -900,6 +983,7 @@ export const createUserWorkspace =
|
||||
* @public
|
||||
*/
|
||||
export async function getInviteLink (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
@ -907,13 +991,15 @@ export async function getInviteLink (
|
||||
emailMask: string,
|
||||
limit: number
|
||||
): Promise<ObjectId> {
|
||||
const { workspace } = decodeToken(token)
|
||||
const { workspace, email } = decodeToken(token)
|
||||
const wsPromise = await getWorkspaceById(db, productId, workspace.name)
|
||||
if (wsPromise === null) {
|
||||
await ctx.error('workspace not found', { workspace, email })
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspace.name })
|
||||
)
|
||||
}
|
||||
await ctx.info('Getting invite link', { workspace: workspace.name, emailMask, limit })
|
||||
const result = await db.collection(INVITE_COLLECTION).insertOne({
|
||||
workspace,
|
||||
exp: Date.now() + exp,
|
||||
@ -946,10 +1032,18 @@ function trimWorkspaceInfo (ws: Workspace): WorkspaceInfo {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getUserWorkspaces (db: Db, productId: string, token: string): Promise<ClientWorkspaceInfo[]> {
|
||||
export async function getUserWorkspaces (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string
|
||||
): Promise<ClientWorkspaceInfo[]> {
|
||||
const { email } = decodeToken(token)
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) return []
|
||||
if (account === null) {
|
||||
await ctx.error('account not found', { email })
|
||||
return []
|
||||
}
|
||||
return (
|
||||
await db
|
||||
.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
@ -964,6 +1058,7 @@ export async function getUserWorkspaces (db: Db, productId: string, token: strin
|
||||
* @public
|
||||
*/
|
||||
export async function getWorkspaceInfo (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
@ -1021,6 +1116,7 @@ async function updateLastVisit (db: Db, ws: Workspace, account: Account): Promis
|
||||
}
|
||||
|
||||
async function getWorkspaceAndAccount (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -1072,6 +1168,7 @@ export async function setRole (
|
||||
* @public
|
||||
*/
|
||||
export async function assignWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -1083,9 +1180,10 @@ export async function assignWorkspace (
|
||||
const email = cleanEmail(_email)
|
||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
if (initWS !== undefined && initWS === workspaceId) {
|
||||
await ctx.error('assign-workspace failed', { email, workspaceId, reason: 'initWs === workspaceId' })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
const workspaceInfo = await getWorkspaceAndAccount(db, productId, email, workspaceId)
|
||||
const workspaceInfo = await getWorkspaceAndAccount(ctx, db, productId, email, workspaceId)
|
||||
|
||||
if (workspaceInfo.account !== null) {
|
||||
await createPersonAccount(
|
||||
@ -1107,6 +1205,8 @@ export async function assignWorkspace (
|
||||
await db
|
||||
.collection(ACCOUNT_COLLECTION)
|
||||
.updateOne({ _id: workspaceInfo.account._id }, { $addToSet: { workspaces: workspaceInfo.workspace._id } })
|
||||
|
||||
await ctx.info('assign-workspace success', { email, workspaceId })
|
||||
return workspaceInfo.workspace
|
||||
}
|
||||
|
||||
@ -1255,6 +1355,7 @@ async function createPersonAccount (
|
||||
* @public
|
||||
*/
|
||||
export async function changePassword (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
@ -1262,12 +1363,13 @@ export async function changePassword (
|
||||
password: string
|
||||
): Promise<void> {
|
||||
const { email } = decodeToken(token)
|
||||
const account = await getAccountInfo(db, email, oldPassword)
|
||||
const account = await getAccountInfo(ctx, db, email, oldPassword)
|
||||
|
||||
const salt = randomBytes(32)
|
||||
const hash = hashWithSalt(password, salt)
|
||||
|
||||
await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } })
|
||||
await ctx.info('change-password success', { email })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1288,11 +1390,12 @@ export async function replacePassword (db: Db, productId: string, email: string,
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function requestPassword (db: Db, productId: string, _email: string): Promise<void> {
|
||||
export async function requestPassword (ctx: MeasureContext, db: Db, productId: string, _email: string): Promise<void> {
|
||||
const email = cleanEmail(_email)
|
||||
const account = await getAccount(db, email)
|
||||
|
||||
if (account === null) {
|
||||
await ctx.info('account not found', { email })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email }))
|
||||
}
|
||||
|
||||
@ -1332,12 +1435,19 @@ export async function requestPassword (db: Db, productId: string, _email: string
|
||||
to
|
||||
})
|
||||
})
|
||||
await ctx.info('recovery email sent', { email, accountEmail: account.email })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function restorePassword (db: Db, productId: string, token: string, password: string): Promise<LoginInfo> {
|
||||
export async function restorePassword (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
password: string
|
||||
): Promise<LoginInfo> {
|
||||
const decode = decodeToken(token)
|
||||
const email = decode.extra?.restore
|
||||
if (email === undefined) {
|
||||
@ -1351,7 +1461,7 @@ export async function restorePassword (db: Db, productId: string, token: string,
|
||||
|
||||
await updatePassword(db, account, password)
|
||||
|
||||
return await login(db, productId, email, password)
|
||||
return await login(ctx, db, productId, email, password)
|
||||
}
|
||||
|
||||
async function updatePassword (db: Db, account: Account, password: string | null): Promise<void> {
|
||||
@ -1364,20 +1474,28 @@ async function updatePassword (db: Db, account: Account, password: string | null
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function removeWorkspace (db: Db, productId: string, email: string, workspaceId: string): Promise<void> {
|
||||
const { workspace, account } = await getWorkspaceAndAccount(db, productId, email, workspaceId)
|
||||
export async function removeWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
email: string,
|
||||
workspaceId: string
|
||||
): Promise<void> {
|
||||
const { workspace, account } = await getWorkspaceAndAccount(ctx, db, productId, email, workspaceId)
|
||||
|
||||
// Add account into workspace.
|
||||
await db.collection(WORKSPACE_COLLECTION).updateOne({ _id: workspace._id }, { $pull: { accounts: account._id } })
|
||||
|
||||
// Add account a workspace
|
||||
await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $pull: { workspaces: workspace._id } })
|
||||
await ctx.info('Workspace removed', { email, workspace })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function checkJoin (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
@ -1388,17 +1506,23 @@ export async function checkJoin (
|
||||
const workspace = await checkInvite(invite, email)
|
||||
const ws = await getWorkspaceById(db, productId, workspace.name)
|
||||
if (ws === null) {
|
||||
await ctx.error('workspace not found', { name: workspace.name, email, inviteId })
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspace.name })
|
||||
)
|
||||
}
|
||||
return await selectWorkspace(db, productId, token, ws?.workspaceUrl ?? ws.workspace, false)
|
||||
return await selectWorkspace(ctx, db, productId, token, ws?.workspaceUrl ?? ws.workspace, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function dropWorkspace (db: Db, productId: string, workspaceId: string): Promise<void> {
|
||||
export async function dropWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
workspaceId: string
|
||||
): Promise<void> {
|
||||
const ws = await getWorkspaceById(db, productId, workspaceId)
|
||||
if (ws === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspaceId }))
|
||||
@ -1407,12 +1531,14 @@ export async function dropWorkspace (db: Db, productId: string, workspaceId: str
|
||||
await db
|
||||
.collection<Account>(ACCOUNT_COLLECTION)
|
||||
.updateMany({ _id: { $in: ws.accounts ?? [] } }, { $pull: { workspaces: ws._id } })
|
||||
|
||||
await ctx.info('Workspace dropped', { workspace: ws.workspace })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function dropAccount (db: Db, productId: string, email: string): Promise<void> {
|
||||
export async function dropAccount (ctx: MeasureContext, db: Db, productId: string, email: string): Promise<void> {
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email }))
|
||||
@ -1425,7 +1551,7 @@ export async function dropAccount (db: Db, productId: string, email: string): Pr
|
||||
|
||||
await Promise.all(
|
||||
workspaces.map(async (ws) => {
|
||||
await deactivatePersonAccount(account.email, ws.workspace, productId)
|
||||
await deactivatePersonAccount(ctx, account.email, ws.workspace, productId)
|
||||
})
|
||||
)
|
||||
|
||||
@ -1433,12 +1559,19 @@ export async function dropAccount (db: Db, productId: string, email: string): Pr
|
||||
await db
|
||||
.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
.updateMany({ _id: { $in: account.workspaces } }, { $pull: { accounts: account._id } })
|
||||
await ctx.info('Account Dropped', { email, account })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function leaveWorkspace (db: Db, productId: string, token: string, email: string): Promise<void> {
|
||||
export async function leaveWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
email: string
|
||||
): Promise<void> {
|
||||
const tokenData = decodeToken(token)
|
||||
|
||||
const currentAccount = await getAccount(db, tokenData.email)
|
||||
@ -1453,7 +1586,7 @@ export async function leaveWorkspace (db: Db, productId: string, token: string,
|
||||
)
|
||||
}
|
||||
|
||||
await deactivatePersonAccount(email, workspace.workspace, workspace.productId)
|
||||
await deactivatePersonAccount(ctx, email, workspace.workspace, workspace.productId)
|
||||
|
||||
const account = tokenData.email !== email ? await getAccount(db, email) : currentAccount
|
||||
if (account !== null) {
|
||||
@ -1464,12 +1597,19 @@ export async function leaveWorkspace (db: Db, productId: string, token: string,
|
||||
.collection<Account>(ACCOUNT_COLLECTION)
|
||||
.updateOne({ _id: account._id }, { $pull: { workspaces: workspace._id } })
|
||||
}
|
||||
await ctx.info('Account removed from workspace', { email, workspace })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function sendInvite (db: Db, productId: string, token: string, email: string): Promise<void> {
|
||||
export async function sendInvite (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
email: string
|
||||
): Promise<void> {
|
||||
const tokenData = decodeToken(token)
|
||||
const currentAccount = await getAccount(db, tokenData.email)
|
||||
if (currentAccount === null) {
|
||||
@ -1483,8 +1623,9 @@ export async function sendInvite (db: Db, productId: string, token: string, emai
|
||||
)
|
||||
}
|
||||
|
||||
const account = await getAccount(db, email)
|
||||
if (account !== null) return
|
||||
// TODO: Why we not send invite if user has account???
|
||||
// const account = await getAccount(db, email)
|
||||
// if (account !== null) return
|
||||
|
||||
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
|
||||
if (sesURL === undefined || sesURL === '') {
|
||||
@ -1498,7 +1639,7 @@ export async function sendInvite (db: Db, productId: string, token: string, emai
|
||||
const expHours = 48
|
||||
const exp = expHours * 60 * 60 * 1000
|
||||
|
||||
const inviteId = await getInviteLink(db, productId, token, exp, email, 1)
|
||||
const inviteId = await getInviteLink(ctx, db, productId, token, exp, email, 1)
|
||||
const link = concatLink(front, `/login/join?inviteId=${inviteId.toString()}`)
|
||||
|
||||
const ws = workspace.workspace
|
||||
@ -1519,9 +1660,15 @@ export async function sendInvite (db: Db, productId: string, token: string, emai
|
||||
to
|
||||
})
|
||||
})
|
||||
await ctx.info('Invite sent', { email, workspace, link })
|
||||
}
|
||||
|
||||
async function deactivatePersonAccount (email: string, workspace: string, productId: string): Promise<void> {
|
||||
async function deactivatePersonAccount (
|
||||
ctx: MeasureContext,
|
||||
email: string,
|
||||
workspace: string,
|
||||
productId: string
|
||||
): Promise<void> {
|
||||
const connection = await connect(getTransactor(), getWorkspaceId(workspace, productId))
|
||||
try {
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
@ -1535,6 +1682,7 @@ async function deactivatePersonAccount (email: string, workspace: string, produc
|
||||
active: false
|
||||
})
|
||||
}
|
||||
await ctx.info('account deactivated', { email, workspace })
|
||||
}
|
||||
} finally {
|
||||
await connection.close()
|
||||
@ -1544,12 +1692,20 @@ async function deactivatePersonAccount (email: string, workspace: string, produc
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AccountMethod = (db: Db, productId: string, request: any, token?: string) => Promise<any>
|
||||
export type AccountMethod = (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
request: any,
|
||||
token?: string
|
||||
) => Promise<any>
|
||||
|
||||
function wrap (f: (db: Db, productId: string, ...args: any[]) => Promise<any>): AccountMethod {
|
||||
return async function (db: Db, productId: string, request: any, token?: string): Promise<any> {
|
||||
function wrap (
|
||||
accountMethod: (ctx: MeasureContext, db: Db, productId: string, ...args: any[]) => Promise<any>
|
||||
): AccountMethod {
|
||||
return async function (ctx: MeasureContext, db: Db, productId: string, request: any, token?: string): Promise<any> {
|
||||
if (token !== undefined) request.params.unshift(token)
|
||||
return await f(db, productId, ...request.params)
|
||||
return await accountMethod(ctx, db, productId, ...request.params)
|
||||
.then((result) => ({ id: request.id, result }))
|
||||
.catch((err) => {
|
||||
const status =
|
||||
@ -1557,9 +1713,9 @@ function wrap (f: (db: Db, productId: string, ...args: any[]) => Promise<any>):
|
||||
? err.status
|
||||
: new Status(Severity.ERROR, platform.status.InternalServerError, {})
|
||||
if (status.code === platform.status.InternalServerError) {
|
||||
console.error(status, err)
|
||||
void ctx.error('error', { status, err })
|
||||
} else {
|
||||
console.error(status)
|
||||
void ctx.error('error', { status })
|
||||
}
|
||||
return {
|
||||
error: status
|
||||
@ -1569,6 +1725,7 @@ function wrap (f: (db: Db, productId: string, ...args: any[]) => Promise<any>):
|
||||
}
|
||||
|
||||
export async function joinWithProvider (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -1602,17 +1759,17 @@ export async function joinWithProvider (
|
||||
return result
|
||||
}
|
||||
|
||||
const wsRes = await assignWorkspace(db, productId, email, workspace.name, false)
|
||||
const result = await selectWorkspace(db, productId, token, wsRes.workspaceUrl ?? wsRes.workspace, false)
|
||||
const wsRes = await assignWorkspace(ctx, db, productId, email, workspace.name, false)
|
||||
const result = await selectWorkspace(ctx, db, productId, token, wsRes.workspaceUrl ?? wsRes.workspace, false)
|
||||
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
|
||||
const newAccount = await createAcc(db, productId, email, null, first, last, true, extra)
|
||||
const newAccount = await createAcc(ctx, db, productId, email, null, first, last, true, extra)
|
||||
const token = generateToken(email, getWorkspaceId('', productId), getExtra(newAccount))
|
||||
const ws = await assignWorkspace(db, productId, email, workspace.name, false)
|
||||
const result = await selectWorkspace(db, productId, token, ws.workspaceUrl ?? ws.workspace, false)
|
||||
const ws = await assignWorkspace(ctx, db, productId, email, workspace.name, false)
|
||||
const result = await selectWorkspace(ctx, db, productId, token, ws.workspaceUrl ?? ws.workspace, false)
|
||||
|
||||
await useInvite(db, inviteId)
|
||||
|
||||
@ -1620,6 +1777,7 @@ export async function joinWithProvider (
|
||||
}
|
||||
|
||||
export async function loginWithProvider (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
_email: string,
|
||||
@ -1645,7 +1803,7 @@ export async function loginWithProvider (
|
||||
return result
|
||||
}
|
||||
|
||||
const newAccount = await createAcc(db, productId, email, null, first, last, true, extra)
|
||||
const newAccount = await createAcc(ctx, db, productId, email, null, first, last, true, extra)
|
||||
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
|
@ -56,7 +56,7 @@ export class MinioService implements StorageAdapter {
|
||||
try {
|
||||
const items = new Map<string, WorkspaceItem>()
|
||||
const list = this.client.listObjects(getBucketId(workspaceId), prefix, true)
|
||||
await new Promise((resolve) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
list.on('data', (data) => {
|
||||
if (data.name !== undefined) {
|
||||
items.set(data.name, { metaData: {}, ...data } as any)
|
||||
@ -66,10 +66,14 @@ export class MinioService implements StorageAdapter {
|
||||
list.destroy()
|
||||
resolve(null)
|
||||
})
|
||||
list.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
return Array.from(items.values())
|
||||
} catch (err: any) {
|
||||
if (((err?.message as string) ?? '').includes('Invalid bucket name')) {
|
||||
const msg = (err?.message as string) ?? ''
|
||||
if (msg.includes('Invalid bucket name') || msg.includes('The specified bucket does not exist')) {
|
||||
return []
|
||||
}
|
||||
throw err
|
||||
@ -98,7 +102,7 @@ export class MinioService implements StorageAdapter {
|
||||
const data = await this.client.getObject(getBucketId(workspaceId), name)
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
await new Promise((resolve) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
@ -111,6 +115,9 @@ export class MinioService implements StorageAdapter {
|
||||
data.destroy()
|
||||
resolve(null)
|
||||
})
|
||||
data.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
return chunks
|
||||
}
|
||||
|
@ -152,5 +152,11 @@ export async function createStorageDataAdapter (
|
||||
if (storage === undefined) {
|
||||
throw new Error('minio storage adapter require minio')
|
||||
}
|
||||
// We need to create bucket if it doesn't exist
|
||||
if (storage !== undefined) {
|
||||
if (!(await storage.exists(workspaceId))) {
|
||||
await storage.make(workspaceId)
|
||||
}
|
||||
}
|
||||
return new StorageBlobAdapter(workspaceId, storage)
|
||||
}
|
||||
|
@ -53,8 +53,12 @@ export class FileModelLogger implements ModelLogger {
|
||||
this.handle = fs.createWriteStream(this.file, { flags: 'a' })
|
||||
}
|
||||
|
||||
log (...data: any[]): void {
|
||||
this.handle.write(data.map((it: any) => JSON.stringify(it)).join(' ') + '\n')
|
||||
log (msg: string, data: any): void {
|
||||
this.handle.write(msg + ' : ' + JSON.stringify(data) + '\n')
|
||||
}
|
||||
|
||||
error (msg: string, data: any): void {
|
||||
this.handle.write(msg + ': ' + JSON.stringify(data) + '\n')
|
||||
}
|
||||
|
||||
close (): void {
|
||||
@ -129,34 +133,32 @@ export async function initModel (
|
||||
await client.connect()
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
|
||||
logger.log('dropping database...', workspaceId)
|
||||
await db.dropDatabase()
|
||||
|
||||
logger.log('creating model...', workspaceId)
|
||||
const model = txes
|
||||
const result = await db.collection(DOMAIN_TX).insertMany(model as Document[])
|
||||
logger.log(`${result.insertedCount} model transactions inserted.`)
|
||||
logger.log('model transactions inserted.', { count: result.insertedCount })
|
||||
|
||||
logger.log('creating data...', transactorUrl)
|
||||
logger.log('creating data...', { transactorUrl })
|
||||
connection = (await connect(transactorUrl, workspaceId, undefined, {
|
||||
model: 'upgrade'
|
||||
model: 'upgrade',
|
||||
admin: 'true'
|
||||
})) as unknown as CoreClient & BackupClient
|
||||
|
||||
try {
|
||||
for (const op of migrateOperations) {
|
||||
logger.log('Migrate', op[0])
|
||||
logger.log('Migrate', { name: op[0] })
|
||||
await op[1].upgrade(connection, logger)
|
||||
}
|
||||
|
||||
// Create update indexes
|
||||
await createUpdateIndexes(connection, db, logger)
|
||||
|
||||
logger.log('create minio bucket')
|
||||
logger.log('create minio bucket', { workspaceId })
|
||||
if (!(await minio.exists(workspaceId))) {
|
||||
await minio.make(workspaceId)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.log(e)
|
||||
} catch (e: any) {
|
||||
logger.error('error', { error: e })
|
||||
}
|
||||
} finally {
|
||||
await client.close()
|
||||
@ -185,19 +187,19 @@ export async function upgradeModel (
|
||||
await client.connect()
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
|
||||
logger.log(`${workspaceId.name}: removing model...`)
|
||||
logger.log('removing model...', { workspaceId: workspaceId.name })
|
||||
// we're preserving accounts (created by core.account.System).
|
||||
const result = await db.collection(DOMAIN_TX).deleteMany({
|
||||
objectSpace: core.space.Model,
|
||||
modifiedBy: core.account.System,
|
||||
objectClass: { $nin: [contact.class.PersonAccount, 'contact:class:EmployeeAccount'] }
|
||||
})
|
||||
logger.log(`${workspaceId.name}: ${result.deletedCount} transactions deleted.`)
|
||||
logger.log('transactions deleted.', { workspaceId: workspaceId.name, count: result.deletedCount })
|
||||
|
||||
logger.log(`${workspaceId.name}: creating model...`)
|
||||
logger.log('creating model...', { workspaceId: workspaceId.name })
|
||||
const model = txes
|
||||
const insert = await db.collection(DOMAIN_TX).insertMany(model as Document[])
|
||||
logger.log(`${workspaceId.name}: ${insert.insertedCount} model transactions inserted.`)
|
||||
logger.log('model transactions inserted.', { workspaceId: workspaceId.name, count: insert.insertedCount })
|
||||
|
||||
const hierarchy = new Hierarchy()
|
||||
const modelDb = new ModelDb(hierarchy)
|
||||
@ -216,10 +218,10 @@ export async function upgradeModel (
|
||||
for (const op of migrateOperations) {
|
||||
const t = Date.now()
|
||||
await op[1].migrate(migrateClient, logger)
|
||||
logger.log(`${workspaceId.name}: migrate:`, op[0], Date.now() - t)
|
||||
logger.log('migrate:', { workspaceId: workspaceId.name, operation: op[0], time: Date.now() - t })
|
||||
}
|
||||
|
||||
logger.log(`${workspaceId.name}: Apply upgrade operations`)
|
||||
logger.log('Apply upgrade operations', { workspaceId: workspaceId.name })
|
||||
|
||||
const connection = await connect(transactorUrl, workspaceId, undefined, {
|
||||
mode: 'backup',
|
||||
@ -233,7 +235,7 @@ export async function upgradeModel (
|
||||
for (const op of migrateOperations) {
|
||||
const t = Date.now()
|
||||
await op[1].upgrade(connection, logger)
|
||||
logger.log(`${workspaceId.name}: upgrade:`, op[0], Date.now() - t)
|
||||
logger.log('upgrade:', { operation: op[0], time: Date.now() - t, workspaceId: workspaceId.name })
|
||||
}
|
||||
|
||||
return connection
|
||||
@ -296,12 +298,12 @@ async function createUpdateIndexes (connection: CoreClient, db: Db, logger: Mode
|
||||
await collection.createIndex(vv)
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.log('error: failed to create index', d, vv, JSON.stringify(err))
|
||||
logger.error('error: failed to create index', { d, vv, err })
|
||||
}
|
||||
bb.push(vv)
|
||||
}
|
||||
if (bb.length > 0) {
|
||||
logger.log('created indexes', d, JSON.stringify(bb))
|
||||
logger.log('created indexes', { d, bb })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ export class MigrateClientImpl implements MigrationClient {
|
||||
}
|
||||
} finally {
|
||||
if (Date.now() - t > 1000) {
|
||||
this.logger.log(`update${Date.now() - t > 5000 ? 'slow' : ''}`, domain, query, Date.now() - t)
|
||||
this.logger.log(`update${Date.now() - t > 5000 ? 'slow' : ''}`, { domain, query, time: Date.now() - t })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,7 +160,7 @@ export class MigrateClientImpl implements MigrationClient {
|
||||
query: DocumentQuery<T>,
|
||||
targetDomain: Domain
|
||||
): Promise<MigrationResult> {
|
||||
this.logger.log('move', sourceDomain, query)
|
||||
this.logger.log('move', { sourceDomain, query })
|
||||
const q = this.translateQuery(query)
|
||||
const cursor = this.db.collection(sourceDomain).find<T>(q)
|
||||
const target = this.db.collection(targetDomain)
|
||||
|
Loading…
Reference in New Issue
Block a user