diff --git a/dev/tool/src/db.ts b/dev/tool/src/db.ts index c3a7ef1192..206ca5972e 100644 --- a/dev/tool/src/db.ts +++ b/dev/tool/src/db.ts @@ -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 { - // 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>((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 { - // 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 { - 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') } diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 48a6b63d5b..dbdb518ec8 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -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') diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index a2238ce1bf..6db32427f8 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -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 } diff --git a/server/account-service/src/migration/migration.ts b/server/account-service/src/migration/migration.ts index 4154d825cf..d29c515d6c 100644 --- a/server/account-service/src/migration/migration.ts +++ b/server/account-service/src/migration/migration.ts @@ -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') diff --git a/server/account/src/collections/mongo.ts b/server/account/src/collections/mongo.ts index a5caa01c0b..3cce30d936 100644 --- a/server/account/src/collections/mongo.ts +++ b/server/account/src/collections/mongo.ts @@ -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 { } async find (query: Query, sort?: Sort, limit?: number): Promise { + return await this.findCursor(query, sort, limit).toArray() + } + + findCursor (query: Query, sort?: Sort, limit?: number): FindCursor { const cursor = this.collection.find(query as Filter) if (sort !== undefined) { @@ -121,7 +133,11 @@ implements DbCollection { cursor.limit(limit) } - return await cursor.toArray() + return cursor.map((doc) => { + delete doc._id + + return doc + }) } async findOne (query: Query): Promise { diff --git a/server/account/src/collections/postgres.ts b/server/account/src/collections/postgres.ts index 53ffb1b635..21877db60f 100644 --- a/server/account/src/collections/postgres.ts +++ b/server/account/src/collections/postgres.ts @@ -612,7 +612,7 @@ export class PostgresAccountDB implements AccountDB { async setPassword (accountUuid: PersonUuid, hash: Buffer, salt: Buffer): Promise { 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 { @@ -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) ); ` diff --git a/server/account/src/index.ts b/server/account/src/index.ts index 26900815b0..1692414fb7 100644 --- a/server/account/src/index.ts +++ b/server/account/src/index.ts @@ -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 diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index 84e1d2d899..49ca82b622 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -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>((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 ( diff --git a/server/account/src/types.ts b/server/account/src/types.ts index 0bd72f1924..cdc5304771 100644 --- a/server/account/src/types.ts +++ b/server/account/src/types.ts @@ -97,8 +97,8 @@ export interface Workspace { branding?: string location?: Location region?: string - createdBy: PersonUuid - billingAccount: PersonUuid + createdBy?: PersonUuid + billingAccount?: PersonUuid createdOn?: Timestamp } diff --git a/server/account/src/utils.ts b/server/account/src/utils.ts index 01ebc8e478..5a59f3ef94 100644 --- a/server/account/src/utils.ts +++ b/server/account/src/utils.ts @@ -1088,3 +1088,36 @@ export function flattenStatus (ws: WorkspaceInfoWithStatus): WorkspaceInfoWithSt export async function cleanExpiredOtp (db: AccountDB): Promise { await db.otp.deleteMany({ expiresOn: { $lte: Date.now() } }) } + +export async function getWorkspaces ( + db: AccountDB, + isDisabled?: boolean | null, + region?: string | null, + mode?: WorkspaceMode | null +): Promise { + const statuses = await db.workspaceStatus.find({}) + const statusesMap = statuses.reduce>((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] + })) +}