UBERF-6194: CLI for rename account (#5067)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-03-27 13:46:34 +07:00 committed by GitHub
parent 5716c053fd
commit 9f929f0290
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 175 additions and 12 deletions

View File

@ -82,4 +82,4 @@ function prepareTools (): {
console.log(`tools git_version: ${process.env.GIT_REVISION ?? ''} model_version: ${process.env.MODEL_VERSION ?? ''}`)
devTool(prepareTools, '')
devTool(prepareTools, process.env.PRODUCT_ID ?? '')

View File

@ -79,6 +79,7 @@ import { checkOrphanWorkspaces } from './cleanOrphan'
import { changeConfiguration } from './configuration'
import { fixMixinForeignAttributes, showMixinForeignAttributes } from './mixin'
import { openAIConfig } from './openai'
import { fixAccountEmails, renameAccount } from './renameAccount'
/**
* @public
@ -162,6 +163,28 @@ export function devTool (
})
})
program
.command('reset-email <email> <newEmail>')
.description('rename account in accounts and all workspaces')
.action(async (email: string, newEmail: string, cmd) => {
const { mongodbUri } = prepareTools()
await withDatabase(mongodbUri, async (db) => {
console.log(`update account ${email} to ${newEmail}`)
await renameAccount(toolCtx, db, productId, transactorUrl, email, newEmail)
})
})
program
.command('fix-email <email> <newEmail>')
.description('fix email in all workspaces to be proper one')
.action(async (email: string, newEmail: string, cmd) => {
const { mongodbUri } = prepareTools()
await withDatabase(mongodbUri, async (db) => {
console.log(`update account ${email} to ${newEmail}`)
await fixAccountEmails(toolCtx, db, productId, transactorUrl, email, newEmail)
})
})
program
.command('assign-workspace <email> <workspace>')
.description('assign workspace')
@ -433,8 +456,16 @@ export function devTool (
.action(async () => {
const { mongodbUri } = prepareTools()
await withDatabase(mongodbUri, async (db) => {
const accountsJSON = JSON.stringify(await listAccounts(db), null, 2)
console.info(accountsJSON)
const workspaces = await listWorkspacesPure(db, productId)
const accounts = await listAccounts(db)
for (const a of accounts) {
const wss = a.workspaces.map((it) => it.toString())
console.info(
a.email,
a.confirmed,
workspaces.filter((it) => wss.includes(it._id.toString())).map((it) => it.workspaceUrl ?? it.workspace)
)
}
})
})

View File

@ -0,0 +1,91 @@
import { type Account, changeEmail, getAccount, listWorkspacesPure, type Workspace } from '@hcengineering/account'
import core, { type MeasureContext, TxOperations } from '@hcengineering/core'
import contact from '@hcengineering/model-contact'
import { connect } from '@hcengineering/server-tool'
import { type Db } from 'mongodb'
export async function renameAccount (
ctx: MeasureContext,
db: Db,
productId: string,
transactorUrl: string,
oldEmail: string,
newEmail: string
): Promise<void> {
const account = await getAccount(db, oldEmail)
if (account == null) {
throw new Error("Account does'n exists")
}
const newAccount = await getAccount(db, newEmail)
if (newAccount != null) {
throw new Error('New Account email already exists:' + newAccount?.email + ' ' + newAccount?._id?.toString())
}
await changeEmail(ctx, db, account, newEmail)
await fixWorkspaceEmails(account, db, productId, transactorUrl, oldEmail, newEmail)
}
export async function fixAccountEmails (
ctx: MeasureContext,
db: Db,
productId: string,
transactorUrl: string,
oldEmail: string,
newEmail: string
): Promise<void> {
const account = await getAccount(db, newEmail)
if (account == null) {
throw new Error("Account does'n exists")
}
await fixWorkspaceEmails(account, db, productId, transactorUrl, oldEmail, newEmail)
}
async function fixWorkspaceEmails (
account: Account,
db: Db,
productId: string,
transactorUrl: string,
oldEmail: string,
newEmail: string
): Promise<void> {
const accountWorkspaces = account.workspaces.map((it) => it.toString())
// We need to update all workspaces
const workspaces = await listWorkspacesPure(db, productId)
for (const ws of workspaces) {
if (!accountWorkspaces.includes(ws._id.toString())) {
continue
}
console.log('checking workspace', ws.workspaceName, ws.workspace)
// Let's connect and update account information.
await fixEmailInWorkspace(transactorUrl, ws, oldEmail, newEmail)
}
}
async function fixEmailInWorkspace (
transactorUrl: string,
ws: Workspace,
oldEmail: string,
newEmail: string
): Promise<void> {
const connection = await connect(transactorUrl, { name: ws.workspace, productId: ws.productId }, undefined, {
mode: 'backup',
model: 'upgrade', // Required for force all clients reload after operation will be complete.
admin: 'true'
})
try {
const personAccount = await connection.findOne(contact.class.PersonAccount, { email: oldEmail })
if (personAccount !== undefined) {
console.log('update account in ', ws.workspace)
const ops = new TxOperations(connection, core.account.ConfigUser)
await ops.update(personAccount, { email: newEmail })
}
} catch (err: any) {
console.error(err)
} finally {
await connection.close()
}
}

View File

@ -13,29 +13,36 @@
// limitations under the License.
-->
<script lang="ts">
import { Channel } from '@hcengineering/contact'
import { Data } from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform'
import { translate } from '@hcengineering/platform'
import { copyTextToClipboard } from '@hcengineering/presentation'
import {
PopupOptions,
themeStore,
Button,
createFocusManager,
FocusHandler,
IconArrowRight,
IconBlueCheck,
IconClose,
registerFocus
IconMoreV,
PopupOptions,
createFocusManager,
getEventPopupPositionElement,
registerFocus,
showPopup,
themeStore
} from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import view from '@hcengineering/view'
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
import plugin from '../plugin'
import IconCopy from './icons/Copy.svelte'
export let value: string = ''
export let placeholder: IntlString
export let editable: boolean | undefined = undefined
export let openable: boolean = false
export let popupOptions: PopupOptions
export let channel: Data<Channel> | Channel
const dispatch = createEventDispatcher()
let input: HTMLInputElement
@ -144,6 +151,26 @@
}}
/>
{/if}
{#if '_id' in channel}
<Button
kind={'ghost'}
size={'small'}
icon={IconMoreV}
on:click={(evt) => {
showPopup(
ContextMenu,
{
object: channel,
includedActions: [view.action.Delete]
},
getEventPopupPositionElement(evt),
() => {
dispatch('close')
}
)
}}
/>
{/if}
</div>
{:else}
<div class="flex-row-center gap-2">

View File

@ -29,11 +29,13 @@
Menu,
closeTooltip,
eventToHTMLElement,
getEventPopupPositionElement,
getFocusManager,
getPopupPositionElement,
showPopup
} from '@hcengineering/ui'
import view, { Action as ViewAction } from '@hcengineering/view'
import { invokeAction } from '@hcengineering/view-resources'
import { ContextMenu, invokeAction } from '@hcengineering/view-resources'
import { createEventDispatcher, tick } from 'svelte'
import { readable, Readable, Writable, writable } from 'svelte/store'
import { channelProviders } from '../utils'
@ -222,7 +224,8 @@
value: item.value,
placeholder: item.placeholder,
editable,
openable: item.presenter ?? item.action ?? false
openable: item.presenter ?? item.action ?? false,
channel: item.channel
},
el,
(result) => {

View File

@ -24,6 +24,7 @@
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let actions: Action[] = []
export let excludedActions: string[] = []
export let includedActions: string[] = []
export let mode: ViewContextType | undefined = undefined
let resActions = actions
@ -41,11 +42,14 @@
remove: 7
}
getActions(client, object, baseMenuClass, mode).then((result) => {
void getActions(client, object, baseMenuClass, mode).then((result) => {
const filtered = result.filter((a) => {
if (excludedActions.includes(a._id)) {
return false
}
if (includedActions.length > 0 && !includedActions.includes(a._id)) {
return false
}
if (a.override && a.override.filter((o) => excludedActions.includes(o)).length > 0) {
return false
}

View File

@ -668,7 +668,6 @@ export async function listWorkspacesPure (db: Db, productId: string): Promise<Wo
(it) => ({ ...it, productId })
)
}
/**
* @public
*/
@ -1372,6 +1371,14 @@ export async function changePassword (
await ctx.info('change-password success', { email })
}
/**
* @public
*/
export async function changeEmail (ctx: MeasureContext, db: Db, account: Account, newEmail: string): Promise<void> {
await db.collection<Account>(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { email: newEmail } })
await ctx.info('change-email success', { email: newEmail })
}
/**
* @public
*/