mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-04 23:04:47 +00:00
UBERF-6194: CLI for rename account (#5067)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
5716c053fd
commit
9f929f0290
@ -82,4 +82,4 @@ function prepareTools (): {
|
|||||||
|
|
||||||
console.log(`tools git_version: ${process.env.GIT_REVISION ?? ''} model_version: ${process.env.MODEL_VERSION ?? ''}`)
|
console.log(`tools git_version: ${process.env.GIT_REVISION ?? ''} model_version: ${process.env.MODEL_VERSION ?? ''}`)
|
||||||
|
|
||||||
devTool(prepareTools, '')
|
devTool(prepareTools, process.env.PRODUCT_ID ?? '')
|
||||||
|
@ -79,6 +79,7 @@ import { checkOrphanWorkspaces } from './cleanOrphan'
|
|||||||
import { changeConfiguration } from './configuration'
|
import { changeConfiguration } from './configuration'
|
||||||
import { fixMixinForeignAttributes, showMixinForeignAttributes } from './mixin'
|
import { fixMixinForeignAttributes, showMixinForeignAttributes } from './mixin'
|
||||||
import { openAIConfig } from './openai'
|
import { openAIConfig } from './openai'
|
||||||
|
import { fixAccountEmails, renameAccount } from './renameAccount'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @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
|
program
|
||||||
.command('assign-workspace <email> <workspace>')
|
.command('assign-workspace <email> <workspace>')
|
||||||
.description('assign workspace')
|
.description('assign workspace')
|
||||||
@ -433,8 +456,16 @@ export function devTool (
|
|||||||
.action(async () => {
|
.action(async () => {
|
||||||
const { mongodbUri } = prepareTools()
|
const { mongodbUri } = prepareTools()
|
||||||
await withDatabase(mongodbUri, async (db) => {
|
await withDatabase(mongodbUri, async (db) => {
|
||||||
const accountsJSON = JSON.stringify(await listAccounts(db), null, 2)
|
const workspaces = await listWorkspacesPure(db, productId)
|
||||||
console.info(accountsJSON)
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
91
dev/tool/src/renameAccount.ts
Normal file
91
dev/tool/src/renameAccount.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -13,29 +13,36 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { Channel } from '@hcengineering/contact'
|
||||||
|
import { Data } from '@hcengineering/core'
|
||||||
import type { IntlString } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
import { copyTextToClipboard } from '@hcengineering/presentation'
|
import { copyTextToClipboard } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
PopupOptions,
|
|
||||||
themeStore,
|
|
||||||
Button,
|
Button,
|
||||||
createFocusManager,
|
|
||||||
FocusHandler,
|
FocusHandler,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconBlueCheck,
|
IconBlueCheck,
|
||||||
IconClose,
|
IconClose,
|
||||||
registerFocus
|
IconMoreV,
|
||||||
|
PopupOptions,
|
||||||
|
createFocusManager,
|
||||||
|
getEventPopupPositionElement,
|
||||||
|
registerFocus,
|
||||||
|
showPopup,
|
||||||
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
|
import { ContextMenu } from '@hcengineering/view-resources'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
|
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
|
||||||
import plugin from '../plugin'
|
import plugin from '../plugin'
|
||||||
import IconCopy from './icons/Copy.svelte'
|
import IconCopy from './icons/Copy.svelte'
|
||||||
|
|
||||||
export let value: string = ''
|
export let value: string = ''
|
||||||
export let placeholder: IntlString
|
export let placeholder: IntlString
|
||||||
export let editable: boolean | undefined = undefined
|
export let editable: boolean | undefined = undefined
|
||||||
export let openable: boolean = false
|
export let openable: boolean = false
|
||||||
export let popupOptions: PopupOptions
|
export let popupOptions: PopupOptions
|
||||||
|
export let channel: Data<Channel> | Channel
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let input: HTMLInputElement
|
let input: HTMLInputElement
|
||||||
@ -144,6 +151,26 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/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>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex-row-center gap-2">
|
<div class="flex-row-center gap-2">
|
||||||
|
@ -29,11 +29,13 @@
|
|||||||
Menu,
|
Menu,
|
||||||
closeTooltip,
|
closeTooltip,
|
||||||
eventToHTMLElement,
|
eventToHTMLElement,
|
||||||
|
getEventPopupPositionElement,
|
||||||
getFocusManager,
|
getFocusManager,
|
||||||
|
getPopupPositionElement,
|
||||||
showPopup
|
showPopup
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import view, { Action as ViewAction } from '@hcengineering/view'
|
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 { createEventDispatcher, tick } from 'svelte'
|
||||||
import { readable, Readable, Writable, writable } from 'svelte/store'
|
import { readable, Readable, Writable, writable } from 'svelte/store'
|
||||||
import { channelProviders } from '../utils'
|
import { channelProviders } from '../utils'
|
||||||
@ -222,7 +224,8 @@
|
|||||||
value: item.value,
|
value: item.value,
|
||||||
placeholder: item.placeholder,
|
placeholder: item.placeholder,
|
||||||
editable,
|
editable,
|
||||||
openable: item.presenter ?? item.action ?? false
|
openable: item.presenter ?? item.action ?? false,
|
||||||
|
channel: item.channel
|
||||||
},
|
},
|
||||||
el,
|
el,
|
||||||
(result) => {
|
(result) => {
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||||
export let actions: Action[] = []
|
export let actions: Action[] = []
|
||||||
export let excludedActions: string[] = []
|
export let excludedActions: string[] = []
|
||||||
|
export let includedActions: string[] = []
|
||||||
export let mode: ViewContextType | undefined = undefined
|
export let mode: ViewContextType | undefined = undefined
|
||||||
let resActions = actions
|
let resActions = actions
|
||||||
|
|
||||||
@ -41,11 +42,14 @@
|
|||||||
remove: 7
|
remove: 7
|
||||||
}
|
}
|
||||||
|
|
||||||
getActions(client, object, baseMenuClass, mode).then((result) => {
|
void getActions(client, object, baseMenuClass, mode).then((result) => {
|
||||||
const filtered = result.filter((a) => {
|
const filtered = result.filter((a) => {
|
||||||
if (excludedActions.includes(a._id)) {
|
if (excludedActions.includes(a._id)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (includedActions.length > 0 && !includedActions.includes(a._id)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (a.override && a.override.filter((o) => excludedActions.includes(o)).length > 0) {
|
if (a.override && a.override.filter((o) => excludedActions.includes(o)).length > 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -668,7 +668,6 @@ export async function listWorkspacesPure (db: Db, productId: string): Promise<Wo
|
|||||||
(it) => ({ ...it, productId })
|
(it) => ({ ...it, productId })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -1372,6 +1371,14 @@ export async function changePassword (
|
|||||||
await ctx.info('change-password success', { email })
|
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
|
* @public
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user