mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-07 02:07:43 +02:00
Merge branch 'develop' into staging-new
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
+188
-179
@@ -1,5 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { type AccountDB, type Workspace, getAccount, getWorkspaceById } from '@hcengineering/account'
|
||||
import {
|
||||
type AccountDB,
|
||||
type MongoAccountDB,
|
||||
type Workspace,
|
||||
getAccount,
|
||||
getWorkspaceById,
|
||||
getWorkspaces
|
||||
} from '@hcengineering/account'
|
||||
import {
|
||||
systemAccountUuid,
|
||||
type BackupClient,
|
||||
@@ -174,191 +181,193 @@ export async function moveAccountDbFromMongoToPG (
|
||||
mongoDb: AccountDB,
|
||||
pgDb: AccountDB
|
||||
): Promise<void> {
|
||||
// TODO: FIXME
|
||||
throw new Error('Not implemented')
|
||||
// [accountId, workspaceId]
|
||||
// const workspaceAssignments: [string, WorkspaceUuid][] = []
|
||||
// const accounts = await listAccounts(mongoDb)
|
||||
// const workspaces = await listWorkspacesPure(mongoDb)
|
||||
// const invites = await listInvites(mongoDb)
|
||||
const mdb = mongoDb as MongoAccountDB
|
||||
|
||||
// for (const mongoAccount of accounts) {
|
||||
// const pgAccount = {
|
||||
// ...mongoAccount,
|
||||
// _id: mongoAccount._id.toString()
|
||||
// }
|
||||
|
||||
// delete (pgAccount as any).workspaces
|
||||
|
||||
// if (pgAccount.createdOn == null) {
|
||||
// pgAccount.createdOn = Date.now()
|
||||
// }
|
||||
|
||||
// if (pgAccount.first == null) {
|
||||
// pgAccount.first = 'NotSet'
|
||||
// }
|
||||
|
||||
// if (pgAccount.last == null) {
|
||||
// pgAccount.last = 'NotSet'
|
||||
// }
|
||||
|
||||
// for (const workspaceString of new Set(mongoAccount.workspaces.map((w) => w.toString()))) {
|
||||
// workspaceAssignments.push([pgAccount._id, workspaceString])
|
||||
// }
|
||||
|
||||
// const exists = await getAccount(pgDb, pgAccount.email)
|
||||
// if (exists === null) {
|
||||
// await pgDb.account.insertOne(pgAccount)
|
||||
// ctx.info('Moved account', { email: pgAccount.email })
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const mongoWorkspace of workspaces) {
|
||||
// const pgWorkspace = {
|
||||
// ...mongoWorkspace,
|
||||
// _id: mongoWorkspace._id.toString()
|
||||
// }
|
||||
|
||||
// if (pgWorkspace.createdOn == null) {
|
||||
// pgWorkspace.createdOn = Date.now()
|
||||
// }
|
||||
|
||||
// // delete deprecated fields
|
||||
// delete (pgWorkspace as any).createProgress
|
||||
// delete (pgWorkspace as any).creating
|
||||
// delete (pgWorkspace as any).productId
|
||||
// delete (pgWorkspace as any).organisation
|
||||
|
||||
// // assigned separately
|
||||
// delete (pgWorkspace as any).accounts
|
||||
|
||||
// const exists = await getWorkspaceById(pgDb, pgWorkspace.workspace)
|
||||
// if (exists === null) {
|
||||
// await pgDb.workspace.insertOne(pgWorkspace)
|
||||
// ctx.info('Moved workspace', {
|
||||
// workspace: pgWorkspace.workspace,
|
||||
// workspaceName: pgWorkspace.workspaceName,
|
||||
// workspaceUrl: pgWorkspace.workspaceUrl
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const mongoInvite of invites) {
|
||||
// const pgInvite = {
|
||||
// ...mongoInvite,
|
||||
// _id: mongoInvite._id.toString()
|
||||
// }
|
||||
|
||||
// const exists = await pgDb.invite.findOne({ _id: pgInvite._id })
|
||||
// if (exists === null) {
|
||||
// await pgDb.invite.insertOne(pgInvite)
|
||||
// }
|
||||
// }
|
||||
|
||||
// const pgAssignments = (await listAccounts(pgDb)).reduce<Record<ObjectId, ObjectId[]>>((assignments, acc) => {
|
||||
// assignments[acc._id] = acc.workspaces
|
||||
|
||||
// return assignments
|
||||
// }, {})
|
||||
// const assignmentsToInsert = workspaceAssignments.filter(
|
||||
// ([accountId, workspaceId]) =>
|
||||
// pgAssignments[accountId] === undefined || !pgAssignments[accountId].includes(workspaceId)
|
||||
// )
|
||||
|
||||
// for (const [accountId, workspaceId] of assignmentsToInsert) {
|
||||
// await pgDb.assignWorkspace(accountId, workspaceId)
|
||||
// }
|
||||
|
||||
// ctx.info('Assignments made', { count: assignmentsToInsert.length })
|
||||
}
|
||||
|
||||
export async function generateUuidMissingWorkspaces (
|
||||
ctx: MeasureMetricsContext,
|
||||
db: AccountDB,
|
||||
dryRun = false
|
||||
): Promise<void> {
|
||||
// TODO: FIXME
|
||||
throw new Error('Not implemented')
|
||||
// const workspaces = await listWorkspacesPure(db)
|
||||
// let updated = 0
|
||||
// for (const ws of workspaces) {
|
||||
// if (ws.uuid !== undefined) continue
|
||||
|
||||
// const uuid = new UUID().toJSON()
|
||||
// if (!dryRun) {
|
||||
// await db.workspace.updateOne({ _id: ws._id }, { uuid })
|
||||
// }
|
||||
// updated++
|
||||
// }
|
||||
// ctx.info('Assigned uuids to workspaces', { updated, total: workspaces.length })
|
||||
}
|
||||
|
||||
export async function updateDataWorkspaceIdToUuid (
|
||||
ctx: MeasureMetricsContext,
|
||||
accountDb: AccountDB,
|
||||
dbUrl: string | undefined,
|
||||
dryRun = false
|
||||
): Promise<void> {
|
||||
if (dbUrl === undefined) {
|
||||
throw new Error('dbUrl is required')
|
||||
}
|
||||
|
||||
const pg = getDBClient(sharedPipelineContextVars, dbUrl)
|
||||
ctx.info('Starting migration of persons...')
|
||||
const personsCursor = mdb.person.findCursor({})
|
||||
try {
|
||||
const pgClient = await pg.getClient()
|
||||
let personsCount = 0
|
||||
while (await personsCursor.hasNext()) {
|
||||
const person = await personsCursor.next()
|
||||
if (person == null) break
|
||||
|
||||
// Generate uuids for all workspaces or verify they exist
|
||||
await generateUuidMissingWorkspaces(ctx, accountDb, dryRun)
|
||||
const exists = await pgDb.person.findOne({ uuid: person.uuid })
|
||||
if (exists == null) {
|
||||
if (person.firstName == null) {
|
||||
person.firstName = 'n/a'
|
||||
}
|
||||
|
||||
const workspaces: Workspace[] = [] // TODO: FIXME await listWorkspacesPure(accountDb)
|
||||
// const noUuidWss = workspaces.filter((ws) => ws.uuid === undefined)
|
||||
// if (noUuidWss.length > 0) {
|
||||
// ctx.error('Workspace uuid is required but not defined', { workspaces: noUuidWss.map((it) => it.workspace) })
|
||||
// throw new Error('workspace uuid is required but not defined')
|
||||
// }
|
||||
if (person.lastName == null) {
|
||||
person.lastName = 'n/a'
|
||||
}
|
||||
|
||||
const res = await pgClient`select t.table_name from information_schema.columns as c
|
||||
join information_schema.tables as t on
|
||||
c.table_catalog = t.table_catalog and
|
||||
c.table_schema = t.table_schema and
|
||||
c.table_name = t.table_name
|
||||
where t.table_type = 'BASE TABLE' and t.table_schema = 'public' and c.column_name = 'workspaceId' and c.data_type <> 'uuid'`
|
||||
const tables: string[] = res.map((r) => r.table_name)
|
||||
|
||||
ctx.info('Tables to be updated: ', { tables })
|
||||
|
||||
for (const table of tables) {
|
||||
ctx.info('Altering table workspaceId type to uuid', { table })
|
||||
|
||||
if (!dryRun) {
|
||||
await retryTxn(pgClient, async (client) => {
|
||||
await client`ALTER TABLE ${client(table)} RENAME COLUMN "workspaceId" TO "workspaceIdOld"`
|
||||
await client`ALTER TABLE ${client(table)} ADD COLUMN "workspaceId" UUID`
|
||||
})
|
||||
|
||||
await retryTxn(pgClient, async (client) => {
|
||||
for (const ws of workspaces) {
|
||||
if (ws.dataId === undefined) continue
|
||||
|
||||
const uuid = ws.uuid
|
||||
|
||||
await client`UPDATE ${client(table)} SET "workspaceId" = ${uuid} WHERE "workspaceIdOld" = ${ws.dataId} OR "workspaceIdOld" = ${uuid}`
|
||||
}
|
||||
})
|
||||
|
||||
await retryTxn(pgClient, async (client) => {
|
||||
await client`ALTER TABLE ${client(table)} ALTER COLUMN "workspaceId" SET NOT NULL`
|
||||
})
|
||||
|
||||
await retryTxn(pgClient, async (client) => {
|
||||
await client`ALTER TABLE ${client(table)} DROP CONSTRAINT ${client(`${table}_pkey`)}`
|
||||
await client`ALTER TABLE ${client(table)} ADD CONSTRAINT ${client(`${table}_pkey`)} PRIMARY KEY ("workspaceId", _id)`
|
||||
})
|
||||
await pgDb.person.insertOne(person)
|
||||
personsCount++
|
||||
if (personsCount % 100 === 0) {
|
||||
ctx.info(`Migrated ${personsCount} persons...`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.info('Done updating workspaceId to uuid')
|
||||
ctx.info(`Migrated ${personsCount} persons`)
|
||||
} finally {
|
||||
pg.close()
|
||||
await personsCursor.close()
|
||||
}
|
||||
|
||||
ctx.info('Starting migration of accounts...')
|
||||
const accountsCursor = mdb.account.findCursor({})
|
||||
try {
|
||||
let accountsCount = 0
|
||||
while (await accountsCursor.hasNext()) {
|
||||
const account = await accountsCursor.next()
|
||||
if (account == null) break
|
||||
|
||||
const exists = await pgDb.account.findOne({ uuid: account.uuid })
|
||||
if (exists == null) {
|
||||
const { hash, salt } = account
|
||||
|
||||
delete account.hash
|
||||
delete account.salt
|
||||
|
||||
await pgDb.account.insertOne(account)
|
||||
if (hash != null && salt != null) {
|
||||
await pgDb.setPassword(account.uuid, hash, salt)
|
||||
}
|
||||
accountsCount++
|
||||
if (accountsCount % 100 === 0) {
|
||||
ctx.info(`Migrated ${accountsCount} accounts...`)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.info(`Migrated ${accountsCount} accounts`)
|
||||
} finally {
|
||||
await accountsCursor.close()
|
||||
}
|
||||
|
||||
ctx.info('Starting migration of social IDs...')
|
||||
const socialIdsCursor = mdb.socialId.findCursor({})
|
||||
try {
|
||||
let socialIdsCount = 0
|
||||
while (await socialIdsCursor.hasNext()) {
|
||||
const socialId = await socialIdsCursor.next()
|
||||
if (socialId == null) break
|
||||
|
||||
const exists = await pgDb.socialId.findOne({ key: socialId.key })
|
||||
if (exists == null) {
|
||||
delete (socialId as any).key
|
||||
|
||||
await pgDb.socialId.insertOne(socialId)
|
||||
socialIdsCount++
|
||||
if (socialIdsCount % 100 === 0) {
|
||||
ctx.info(`Migrated ${socialIdsCount} social IDs...`)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.info(`Migrated ${socialIdsCount} social IDs`)
|
||||
} finally {
|
||||
await socialIdsCursor.close()
|
||||
}
|
||||
|
||||
ctx.info('Starting migration of account events...')
|
||||
const accountEventsCursor = mdb.accountEvent.findCursor({})
|
||||
try {
|
||||
let eventsCount = 0
|
||||
while (await accountEventsCursor.hasNext()) {
|
||||
const accountEvent = await accountEventsCursor.next()
|
||||
if (accountEvent == null) break
|
||||
|
||||
const exists = await pgDb.accountEvent.findOne({
|
||||
accountUuid: accountEvent.accountUuid,
|
||||
eventType: accountEvent.eventType,
|
||||
time: accountEvent.time
|
||||
})
|
||||
if (exists == null) {
|
||||
await pgDb.accountEvent.insertOne(accountEvent)
|
||||
eventsCount++
|
||||
if (eventsCount % 100 === 0) {
|
||||
ctx.info(`Migrated ${eventsCount} account events...`)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.info(`Migrated ${eventsCount} account events`)
|
||||
} finally {
|
||||
await accountEventsCursor.close()
|
||||
}
|
||||
|
||||
ctx.info('Starting migration of workspaces...')
|
||||
const workspacesCursor = mdb.workspace.findCursor({})
|
||||
try {
|
||||
let workspacesCount = 0
|
||||
let membersCount = 0
|
||||
while (await workspacesCursor.hasNext()) {
|
||||
const workspace = await workspacesCursor.next()
|
||||
if (workspace == null) break
|
||||
|
||||
const exists = await pgDb.workspace.findOne({ uuid: workspace.uuid })
|
||||
if (exists != null) continue
|
||||
|
||||
const status = workspace.status
|
||||
if (status == null) continue
|
||||
delete (workspace as any).status
|
||||
|
||||
if (workspace.createdBy === 'N/A') {
|
||||
delete workspace.createdBy
|
||||
}
|
||||
|
||||
if (workspace.billingAccount === 'N/A') {
|
||||
delete workspace.billingAccount
|
||||
}
|
||||
|
||||
if (workspace.createdOn == null) {
|
||||
delete workspace.createdOn
|
||||
}
|
||||
|
||||
await pgDb.createWorkspace(workspace, status)
|
||||
workspacesCount++
|
||||
|
||||
const members = await mdb.getWorkspaceMembers(workspace.uuid)
|
||||
for (const member of members) {
|
||||
const alreadyAssigned = await pgDb.getWorkspaceRole(member.person, workspace.uuid)
|
||||
if (alreadyAssigned != null) continue
|
||||
|
||||
await pgDb.assignWorkspace(member.person, workspace.uuid, member.role)
|
||||
membersCount++
|
||||
}
|
||||
|
||||
if (workspacesCount % 100 === 0) {
|
||||
ctx.info(`Migrated ${workspacesCount} invites...`)
|
||||
}
|
||||
}
|
||||
ctx.info(`Migrated ${workspacesCount} workspaces with ${membersCount} member assignments`)
|
||||
} finally {
|
||||
await workspacesCursor.close()
|
||||
}
|
||||
|
||||
ctx.info('Starting migration of invites...')
|
||||
const invitesCursor = mdb.invite.findCursor({})
|
||||
try {
|
||||
let invitesCount = 0
|
||||
while (await invitesCursor.hasNext()) {
|
||||
const invite = await invitesCursor.next()
|
||||
if (invite == null) break
|
||||
if (invite.migratedFrom == null) {
|
||||
invite.migratedFrom = invite.id
|
||||
}
|
||||
|
||||
delete (invite as any).id
|
||||
|
||||
const exists = await pgDb.invite.findOne({ migratedFrom: invite.migratedFrom })
|
||||
if (exists == null) {
|
||||
await pgDb.invite.insertOne(invite)
|
||||
invitesCount++
|
||||
if (invitesCount % 100 === 0) {
|
||||
ctx.info(`Migrated ${invitesCount} invites...`)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.info(`Migrated ${invitesCount} invites`)
|
||||
} finally {
|
||||
await invitesCursor.close()
|
||||
}
|
||||
|
||||
ctx.info('Account database migration completed')
|
||||
}
|
||||
|
||||
+14
-36
@@ -68,12 +68,13 @@ import {
|
||||
shutdownPostgres
|
||||
} from '@hcengineering/postgres'
|
||||
import type { StorageAdapter } from '@hcengineering/server-core'
|
||||
import { getAccountDBUrl } from './__start'
|
||||
import { getAccountDBUrl, getMongoDBUrl } from './__start'
|
||||
// import { fillGithubUsers, fixAccountEmails, renameAccount } from './account'
|
||||
import { changeConfiguration } from './configuration'
|
||||
import { reindexWorkspace } from './fulltext'
|
||||
|
||||
import { getToolToken, getWorkspace, getWorkspaceTransactorEndpoint } from './utils'
|
||||
import { moveAccountDbFromMongoToPG } from './db'
|
||||
|
||||
const colorConstants = {
|
||||
colorRed: '\u001b[31m',
|
||||
@@ -2094,20 +2095,20 @@ export function devTool (
|
||||
// }
|
||||
// )
|
||||
|
||||
// program.command('move-account-db-to-pg').action(async () => {
|
||||
// const { dbUrl } = prepareTools()
|
||||
// const mongodbUri = getMongoDBUrl()
|
||||
program.command('move-account-db-to-pg').action(async () => {
|
||||
const { dbUrl } = prepareTools()
|
||||
const mongodbUri = getMongoDBUrl()
|
||||
|
||||
// if (mongodbUri === dbUrl) {
|
||||
// throw new Error('MONGO_URL and DB_URL are the same')
|
||||
// }
|
||||
if (mongodbUri === dbUrl) {
|
||||
throw new Error('MONGO_URL and DB_URL are the same')
|
||||
}
|
||||
|
||||
// await withAccountDatabase(async (pgDb) => {
|
||||
// await withAccountDatabase(async (mongoDb) => {
|
||||
// await moveAccountDbFromMongoToPG(toolCtx, mongoDb, pgDb)
|
||||
// }, mongodbUri)
|
||||
// }, dbUrl)
|
||||
// })
|
||||
await withAccountDatabase(async (pgDb) => {
|
||||
await withAccountDatabase(async (mongoDb) => {
|
||||
await moveAccountDbFromMongoToPG(toolCtx, mongoDb, pgDb)
|
||||
}, mongodbUri)
|
||||
}, dbUrl)
|
||||
})
|
||||
|
||||
// program
|
||||
// .command('perfomance')
|
||||
@@ -2166,29 +2167,6 @@ export function devTool (
|
||||
// })
|
||||
// })
|
||||
|
||||
// program
|
||||
// .command('generate-uuid-workspaces')
|
||||
// .description('generate uuids for all workspaces which are missing it')
|
||||
// .option('-d, --dryrun', 'Dry run', false)
|
||||
// .action(async (cmd: { dryrun: boolean }) => {
|
||||
// await withAccountDatabase(async (db) => {
|
||||
// console.log('generate uuids for all workspaces which are missing it')
|
||||
// await generateUuidMissingWorkspaces(toolCtx, db, cmd.dryrun)
|
||||
// })
|
||||
// })
|
||||
|
||||
// program
|
||||
// .command('update-data-wsid-to-uuid')
|
||||
// .description('updates workspaceId in pg/cr to uuid')
|
||||
// .option('-d, --dryrun', 'Dry run', false)
|
||||
// .action(async (cmd: { dryrun: boolean }) => {
|
||||
// await withAccountDatabase(async (db) => {
|
||||
// console.log('updates workspaceId in pg/cr to uuid')
|
||||
// const { dbUrl } = prepareTools()
|
||||
// await updateDataWorkspaceIdToUuid(toolCtx, db, dbUrl, cmd.dryrun)
|
||||
// })
|
||||
// })
|
||||
|
||||
// program
|
||||
// .command('add-controlled-doc-rank-mongo')
|
||||
// .description('add rank to controlled documents')
|
||||
|
||||
@@ -794,8 +794,8 @@ export interface WorkspaceInfo {
|
||||
region?: string
|
||||
branding?: string
|
||||
createdOn: number
|
||||
createdBy: PersonUuid
|
||||
billingAccount: PersonUuid
|
||||
createdBy?: PersonUuid // Should always be set for NEW workspaces
|
||||
billingAccount?: PersonUuid // Should always be set for NEW workspaces
|
||||
}
|
||||
|
||||
export interface BackupStatus {
|
||||
@@ -820,7 +820,7 @@ export interface WorkspaceInfoWithStatus extends WorkspaceInfo {
|
||||
}
|
||||
|
||||
export interface WorkspaceMemberInfo {
|
||||
person: string
|
||||
person: PersonUuid
|
||||
role: AccountRole
|
||||
}
|
||||
|
||||
|
||||
@@ -45,45 +45,53 @@ export async function migrateFromOldAccounts (oldAccsUrl: string, accountDB: Acc
|
||||
console.log('Migrating accounts database from old accounts')
|
||||
let accountsProcessed = 0
|
||||
const accountsCursor = oldAccountDb.account.findCursor({})
|
||||
while (await accountsCursor.hasNext()) {
|
||||
const account = await accountsCursor.next()
|
||||
if (account == null) {
|
||||
break
|
||||
}
|
||||
try {
|
||||
while (await accountsCursor.hasNext()) {
|
||||
const account = await accountsCursor.next()
|
||||
if (account == null) {
|
||||
break
|
||||
}
|
||||
|
||||
const accountUuid = await migrateAccount(account, accountDB)
|
||||
if (accountUuid == null) {
|
||||
console.log('Account not migrated', account)
|
||||
continue
|
||||
}
|
||||
accountsIdToUuid[account._id.toString()] = accountUuid
|
||||
accountsEmailToUuid[account.email] = accountUuid
|
||||
const accountUuid = await migrateAccount(account, accountDB)
|
||||
if (accountUuid == null) {
|
||||
console.log('Account not migrated', account)
|
||||
continue
|
||||
}
|
||||
accountsIdToUuid[account._id.toString()] = accountUuid
|
||||
accountsEmailToUuid[account.email] = accountUuid
|
||||
|
||||
accountsProcessed++
|
||||
if (accountsProcessed % 100 === 0) {
|
||||
console.log('Processed accounts:', accountsProcessed)
|
||||
accountsProcessed++
|
||||
if (accountsProcessed % 100 === 0) {
|
||||
console.log('Processed accounts:', accountsProcessed)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await accountsCursor.close()
|
||||
}
|
||||
|
||||
console.log('Total accounts processed:', accountsProcessed)
|
||||
|
||||
let processedWorkspaces = 0
|
||||
const workspacesCursor = oldAccountDb.workspace.findCursor({})
|
||||
while (await workspacesCursor.hasNext()) {
|
||||
const workspace = await workspacesCursor.next()
|
||||
if (workspace == null) {
|
||||
break
|
||||
}
|
||||
try {
|
||||
while (await workspacesCursor.hasNext()) {
|
||||
const workspace = await workspacesCursor.next()
|
||||
if (workspace == null) {
|
||||
break
|
||||
}
|
||||
|
||||
const workspaceUuid = await migrateWorkspace(workspace, accountDB, accountsIdToUuid, accountsEmailToUuid)
|
||||
const workspaceUuid = await migrateWorkspace(workspace, accountDB, accountsIdToUuid, accountsEmailToUuid)
|
||||
|
||||
if (workspaceUuid !== undefined) {
|
||||
workspacesIdToUuid[workspace.workspace] = workspaceUuid
|
||||
}
|
||||
processedWorkspaces++
|
||||
if (processedWorkspaces % 100 === 0) {
|
||||
console.log('Processed workspaces:', processedWorkspaces)
|
||||
if (workspaceUuid !== undefined) {
|
||||
workspacesIdToUuid[workspace.workspace] = workspaceUuid
|
||||
}
|
||||
processedWorkspaces++
|
||||
if (processedWorkspaces % 100 === 0) {
|
||||
console.log('Processed workspaces:', processedWorkspaces)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await workspacesCursor.close()
|
||||
}
|
||||
|
||||
console.log('Total workspaces processed:', processedWorkspaces)
|
||||
@@ -91,32 +99,37 @@ export async function migrateFromOldAccounts (oldAccsUrl: string, accountDB: Acc
|
||||
|
||||
let invitesProcessed = 0
|
||||
const invitesCursor = oldAccountDb.invite.findCursor({})
|
||||
while (await invitesCursor.hasNext()) {
|
||||
const invite = await invitesCursor.next()
|
||||
if (invite == null) {
|
||||
break
|
||||
}
|
||||
try {
|
||||
while (await invitesCursor.hasNext()) {
|
||||
const invite = await invitesCursor.next()
|
||||
if (invite == null) {
|
||||
break
|
||||
}
|
||||
|
||||
const workspaceUuid = workspacesIdToUuid[invite.workspace.name]
|
||||
if (workspaceUuid === undefined) {
|
||||
console.log('No workspace with id', invite.workspace.name, 'found for invite', invite._id)
|
||||
continue
|
||||
}
|
||||
const workspaceUuid = workspacesIdToUuid[invite.workspace.name]
|
||||
if (workspaceUuid === undefined) {
|
||||
console.log('No workspace with id', invite.workspace.name, 'found for invite', invite._id)
|
||||
continue
|
||||
}
|
||||
|
||||
await accountDB.invite.insertOne({
|
||||
migratedFrom: invite._id.toString(),
|
||||
workspaceUuid,
|
||||
expiresOn: invite.exp,
|
||||
emailPattern: invite.emailMask,
|
||||
remainingUses: invite.limit,
|
||||
role: invite.role ?? AccountRole.User
|
||||
})
|
||||
await accountDB.invite.insertOne({
|
||||
migratedFrom: invite._id.toString(),
|
||||
workspaceUuid,
|
||||
expiresOn: invite.exp,
|
||||
emailPattern: invite.emailMask,
|
||||
remainingUses: invite.limit,
|
||||
role: invite.role ?? AccountRole.User
|
||||
})
|
||||
|
||||
invitesProcessed++
|
||||
if (invitesProcessed % 100 === 0) {
|
||||
console.log('Processed invites:', invitesProcessed)
|
||||
invitesProcessed++
|
||||
if (invitesProcessed % 100 === 0) {
|
||||
console.log('Processed invites:', invitesProcessed)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await invitesCursor.close()
|
||||
}
|
||||
|
||||
console.log('Total invites processed:', invitesProcessed)
|
||||
await oldAccountDb.migration.insertOne({ key: migrationKey, completed: true })
|
||||
console.log('Migration of accounts database from old accounts COMPLETED')
|
||||
|
||||
@@ -13,7 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
import { UUID } from 'mongodb'
|
||||
import type { Collection, CreateIndexesOptions, Db, Filter, OptionalUnlessRequiredId, Sort as RawSort } from 'mongodb'
|
||||
import type {
|
||||
Collection,
|
||||
CreateIndexesOptions,
|
||||
Db,
|
||||
Filter,
|
||||
FindCursor,
|
||||
OptionalUnlessRequiredId,
|
||||
Sort as RawSort
|
||||
} from 'mongodb'
|
||||
import {
|
||||
type Person,
|
||||
type WorkspaceMemberInfo,
|
||||
@@ -111,6 +119,10 @@ implements DbCollection<T> {
|
||||
}
|
||||
|
||||
async find (query: Query<T>, sort?: Sort<T>, limit?: number): Promise<T[]> {
|
||||
return await this.findCursor(query, sort, limit).toArray()
|
||||
}
|
||||
|
||||
findCursor (query: Query<T>, sort?: Sort<T>, limit?: number): FindCursor<T> {
|
||||
const cursor = this.collection.find<T>(query as Filter<T>)
|
||||
|
||||
if (sort !== undefined) {
|
||||
@@ -121,7 +133,11 @@ implements DbCollection<T> {
|
||||
cursor.limit(limit)
|
||||
}
|
||||
|
||||
return await cursor.toArray()
|
||||
return cursor.map((doc) => {
|
||||
delete doc._id
|
||||
|
||||
return doc
|
||||
})
|
||||
}
|
||||
|
||||
async findOne (query: Query<T>): Promise<T | null> {
|
||||
|
||||
@@ -612,7 +612,7 @@ export class PostgresAccountDB implements AccountDB {
|
||||
|
||||
async setPassword (accountUuid: PersonUuid, hash: Buffer, salt: Buffer): Promise<void> {
|
||||
await this
|
||||
.client`UPSERT INTO ${this.client(this.account.getPasswordsTableName())} (account_uuid, hash, salt) VALUES (${accountUuid}, ${hash as unknown as Uint8Array}, ${salt as unknown as Uint8Array})`
|
||||
.client`UPSERT INTO ${this.client(this.account.getPasswordsTableName())} (account_uuid, hash, salt) VALUES (${accountUuid}, ${hash.buffer as any}::bytea, ${salt.buffer as any}::bytea)`
|
||||
}
|
||||
|
||||
async resetPassword (accountUuid: PersonUuid): Promise<void> {
|
||||
@@ -639,9 +639,9 @@ export class PostgresAccountDB implements AccountDB {
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
/* ======= T Y P E S ======= */
|
||||
CREATE TYPE global_account.social_id_type AS ENUM ('email', 'github', 'google', 'phone', 'oidc', 'huly', 'telegram');
|
||||
CREATE TYPE global_account.location AS ENUM ('kv', 'weur', 'eeur', 'wnam', 'enam', 'apac');
|
||||
CREATE TYPE global_account.workspace_role AS ENUM ('OWNER', 'MAINTAINER', 'USER', 'GUEST', 'DOCGUEST');
|
||||
CREATE TYPE IF NOT EXISTS global_account.social_id_type AS ENUM ('email', 'github', 'google', 'phone', 'oidc', 'huly', 'telegram');
|
||||
CREATE TYPE IF NOT EXISTS global_account.location AS ENUM ('kv', 'weur', 'eeur', 'wnam', 'enam', 'apac');
|
||||
CREATE TYPE IF NOT EXISTS global_account.workspace_role AS ENUM ('OWNER', 'MAINTAINER', 'USER', 'GUEST', 'DOCGUEST');
|
||||
|
||||
/* ======= P E R S O N ======= */
|
||||
CREATE TABLE IF NOT EXISTS global_account.person (
|
||||
@@ -702,9 +702,9 @@ export class PostgresAccountDB implements AccountDB {
|
||||
branding STRING,
|
||||
location global_account.location,
|
||||
region STRING,
|
||||
created_by UUID NOT NULL, -- account uuid
|
||||
created_by UUID, -- account uuid
|
||||
created_on BIGINT NOT NULL DEFAULT current_epoch_ms(),
|
||||
billing_account UUID NOT NULL,
|
||||
billing_account UUID,
|
||||
CONSTRAINT workspace_pk PRIMARY KEY (uuid),
|
||||
CONSTRAINT workspace_url_unique UNIQUE (url),
|
||||
CONSTRAINT workspace_created_by_fk FOREIGN KEY (created_by) REFERENCES global_account.account(uuid),
|
||||
@@ -759,8 +759,10 @@ export class PostgresAccountDB implements AccountDB {
|
||||
email_pattern STRING,
|
||||
remaining_uses INT2,
|
||||
role global_account.workspace_role NOT NULL DEFAULT 'USER',
|
||||
migrated_from STRING,
|
||||
CONSTRAINT invite_pk PRIMARY KEY (id),
|
||||
INDEX workspace_invite_idx (workspace_uuid),
|
||||
INDEX migrated_from_idx (migrated_from),
|
||||
CONSTRAINT invite_workspace_fk FOREIGN KEY (workspace_uuid) REFERENCES global_account.workspace(uuid)
|
||||
);
|
||||
`
|
||||
|
||||
@@ -19,4 +19,5 @@ export * from './operations'
|
||||
export * from './plugin'
|
||||
export * from './utils'
|
||||
export * from './types'
|
||||
export type { MongoAccountDB } from './collections/mongo'
|
||||
export default accountPlugin
|
||||
|
||||
@@ -85,7 +85,8 @@ import {
|
||||
selectWorkspace,
|
||||
doJoinByInvite,
|
||||
getWorkspacesInfoWithStatusByIds,
|
||||
getSocialIdByKey
|
||||
getSocialIdByKey,
|
||||
getWorkspaces
|
||||
} from './utils'
|
||||
|
||||
// Move to config?
|
||||
@@ -842,29 +843,7 @@ export async function listWorkspaces (
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
|
||||
const statuses = await db.workspaceStatus.find({})
|
||||
const statusesMap = statuses.reduce<Record<string, WorkspaceStatus>>((sm, s) => {
|
||||
sm[s.workspaceUuid] = s
|
||||
return sm
|
||||
}, {})
|
||||
|
||||
const workspaces = (await db.workspace.find(region != null ? { region } : {})).filter((it) => {
|
||||
const status = statusesMap[it.uuid]
|
||||
if (status.isDisabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (mode != null) {
|
||||
return status.mode === mode
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return workspaces.map((it) => ({
|
||||
...it,
|
||||
status: statusesMap[it.uuid]
|
||||
}))
|
||||
return await getWorkspaces(db, false, region, mode)
|
||||
}
|
||||
|
||||
export async function performWorkspaceOperation (
|
||||
|
||||
@@ -97,8 +97,8 @@ export interface Workspace {
|
||||
branding?: string
|
||||
location?: Location
|
||||
region?: string
|
||||
createdBy: PersonUuid
|
||||
billingAccount: PersonUuid
|
||||
createdBy?: PersonUuid
|
||||
billingAccount?: PersonUuid
|
||||
createdOn?: Timestamp
|
||||
}
|
||||
|
||||
|
||||
@@ -1088,3 +1088,36 @@ export function flattenStatus (ws: WorkspaceInfoWithStatus): WorkspaceInfoWithSt
|
||||
export async function cleanExpiredOtp (db: AccountDB): Promise<void> {
|
||||
await db.otp.deleteMany({ expiresOn: { $lte: Date.now() } })
|
||||
}
|
||||
|
||||
export async function getWorkspaces (
|
||||
db: AccountDB,
|
||||
isDisabled?: boolean | null,
|
||||
region?: string | null,
|
||||
mode?: WorkspaceMode | null
|
||||
): Promise<WorkspaceInfoWithStatus[]> {
|
||||
const statuses = await db.workspaceStatus.find({})
|
||||
const statusesMap = statuses.reduce<Record<string, WorkspaceStatus>>((sm, s) => {
|
||||
sm[s.workspaceUuid] = s
|
||||
return sm
|
||||
}, {})
|
||||
|
||||
const workspaces = (await db.workspace.find(region != null ? { region } : {})).filter((it) => {
|
||||
const status = statusesMap[it.uuid]
|
||||
if (isDisabled === true) {
|
||||
return status.isDisabled
|
||||
} else if (isDisabled === false) {
|
||||
return !status.isDisabled
|
||||
}
|
||||
|
||||
if (mode != null) {
|
||||
return status.mode === mode
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return workspaces.map((it) => ({
|
||||
...it,
|
||||
status: statusesMap[it.uuid]
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user