Qfix: adding/deleting social ids (#9466)

Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
Alexey Zinoviev
2025-07-04 19:15:37 +07:00
committed by GitHub
parent 654f0b1d61
commit 582bfdf26e
4 changed files with 79 additions and 35 deletions
+31 -26
View File
@@ -33,7 +33,8 @@ import {
Ref,
SocialId,
toIdMap,
TxFactory
TxFactory,
DocumentUpdate
} from '@hcengineering/core'
import { getMetadata } from '@hcengineering/platform'
import { ColorDefinition } from '@hcengineering/ui'
@@ -459,7 +460,7 @@ export async function ensureEmployeeForPerson (
for (const socialId of socialIds) {
const existing = existingIdentifiers.get(socialId._id as SocialIdentityRef)
if (existing === undefined) {
if (existing == null) {
await ctx.with('create-social-identity', {}, async () => {
if (personRef === undefined) {
// something went wrong
@@ -491,41 +492,45 @@ export async function ensureEmployeeForPerson (
await client.tx(createSocialIdTx)
})
} else {
// This social identity must be attached to the correct person. If it's not the case, something is wrong.
// personRef must be readonly after creation and must NEVER be changed.
if (existing.attachedTo !== personRef) {
throw new Error('Social identity is attached to the wrong person')
// If not confirmed locally can be attached to a different person (persons merge scenario)
// Confirmed social identity should not be attached to a different person for now
// It will change with accounts merge function
if (existing.verifiedOn != null && existing.attachedTo !== personRef) {
throw new Error('Confirmed social identity is attached to the wrong person')
}
// Check and update if needed. It can:
// 1. Become verified (changes verifiedOn)
if (existing.verifiedOn == null) {
const updateSocialIdentityTx = txFactory.createTxUpdateDoc(
contact.class.SocialIdentity,
contact.space.Contacts,
existing._id,
{
verifiedOn: socialId.verifiedOn,
value: socialId.value,
key: socialId.key,
isDeleted: socialId.isDeleted
}
)
// 1. Become verified (maybe with persons merge) (changes verifiedOn, attachedTo)
const sidUpdate: DocumentUpdate<SocialIdentity> = {}
let needUpdate = false
await client.tx(updateSocialIdentityTx)
// become verified
if (existing.verifiedOn == null) {
sidUpdate.verifiedOn = socialId.verifiedOn
needUpdate = true
}
// 2. Become deleted (changes value/key/isDeleted)
// merged from another person
if (existing.attachedTo !== personRef) {
sidUpdate.attachedTo = personRef
// Bump collection in Person?
needUpdate = true
}
// become deleted
if (existing.isDeleted !== socialId.isDeleted && socialId.isDeleted === true) {
sidUpdate.value = socialId.value
sidUpdate.key = socialId.key
sidUpdate.isDeleted = socialId.isDeleted
needUpdate = true
}
if (needUpdate) {
const updateSocialIdentityTx = txFactory.createTxUpdateDoc(
contact.class.SocialIdentity,
contact.space.Contacts,
existing._id,
{
value: socialId.value,
key: socialId.key,
isDeleted: socialId.isDeleted
}
sidUpdate
)
await client.tx(updateSocialIdentityTx)
@@ -14,11 +14,12 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import contact, { SocialIdentityProvider } from '@hcengineering/contact'
import contact, { getCurrentEmployee, SocialIdentityProvider, SocialIdentityRef } from '@hcengineering/contact'
import { EditBox, Label, Button, CodeForm, TimeLeft, Status as StatusControl } from '@hcengineering/ui'
import { OtpInfo } from '@hcengineering/account-client'
import { getCurrentAccount, setCurrentAccount, SocialId, Timestamp } from '@hcengineering/core'
import { buildSocialIdString, getCurrentAccount, setCurrentAccount, SocialId, Timestamp } from '@hcengineering/core'
import { OK, PlatformError, Severity, Status, unknownError } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import AddSocialId from './AddSocialId.svelte'
import setting from '../../plugin'
@@ -31,6 +32,7 @@
let otpInfo: OtpInfo | null = null
const dispatch = createEventDispatcher()
const client = getClient()
const accountClient = getAccountClient()
const codeFields = [
{ id: 'code-1', name: 'code-1', optional: false },
@@ -89,6 +91,7 @@
try {
const newSocialIdLoginInfo = await accountClient.validateOtp(email, code, undefined, 'verify')
const currPerson = getCurrentEmployee()
const currAcc = getCurrentAccount()
const socialIds = await accountClient.getSocialIds()
@@ -98,6 +101,36 @@
return
}
// Create/update new social identity in the workspace
const existing = await client.findOne(contact.class.SocialIdentity, {
_id: newSocialIdLoginInfo.socialId as SocialIdentityRef
})
if (existing != null) {
// It can only exist if it were attached to an existing person and now this person was merged into the current account
if (existing.attachedTo !== currPerson) {
await client.updateDoc(contact.class.SocialIdentity, contact.space.Contacts, existing._id, {
attachedTo: currPerson,
verifiedOn: newSocialId.verifiedOn
})
}
} else {
await client.addCollection(
contact.class.SocialIdentity,
contact.space.Contacts,
currPerson,
contact.class.Person,
'socialIds',
{
type: newSocialId.type,
value: newSocialId.value,
key: buildSocialIdString(newSocialId), // TODO: fill it in trigger or on DB level as stored calculated column or smth?
verifiedOn: newSocialId.verifiedOn,
isDeleted: newSocialId.isDeleted
},
newSocialId._id as SocialIdentityRef
)
}
setCurrentAccount({
...currAcc,
fullSocialIds: socialIds,
@@ -44,7 +44,11 @@
async function doRelease (): Promise<void> {
try {
const currSocialIds = currAcc.fullSocialIds
// Important to always get current account in the callback to avoid race conditions
// with the account from the reactive variable
// W/o it it was bringing back previously deleted social id if two are deleted in a row
const currentAccount = getCurrentAccount()
const currSocialIds = currentAccount.fullSocialIds
const deletedSocialId = await accountClient.releaseSocialId(undefined, socialId.type, socialId.value, true)
@@ -65,12 +69,14 @@
}
const newPrimarySocialId = pickPrimarySocialId(newSocialIds)._id
setCurrentAccount({
...currAcc,
// Don't need to update social ids because deleted social id retains its' _id
const updatedAccount = {
...currentAccount,
fullSocialIds: newSocialIds,
primarySocialId: newPrimarySocialId
})
currAcc = getCurrentAccount()
}
setCurrentAccount(updatedAccount)
currAcc = updatedAccount
dispatch('released')
} catch (err: any) {
console.error(err)
@@ -108,7 +114,7 @@
<div class="icon"><Icon size="full" {icon} /></div>
<div class="flex-col flex-gap-0-5">
<div>{socialId.value}</div>
<div>{socialId.displayValue ?? socialId.value}</div>
<div class="type"><Label label={socialIdProvider.label} /></div>
</div>
@@ -79,7 +79,7 @@
: loginSocialTypes.includes(socialId.type)
? !onlyLogin
: true}
{#if socialIdProvider}
{#if socialIdProvider != null && socialId.type !== SocialIdType.HULY}
<div class="item">
<SocialIdPresenter {socialId} {socialIdProvider} {canRelease} on:released={handleAccountUpdated} />
</div>