@@ -21,7 +21,7 @@ import {
2121import { QueryExecutor , createMember , dbStoreQx , updateMember } from '@crowd/data-access-layer'
2222import {
2323 findIdentitiesForMembers ,
24- findMemberIdByVerifiedIdentity ,
24+ findMembersByIdentities ,
2525 findMembersByVerifiedUsernames ,
2626} from '@crowd/data-access-layer'
2727import { DbStore } from '@crowd/data-access-layer/src/database'
@@ -137,44 +137,59 @@ export default class MemberService extends LoggerBase {
137137 } catch ( err ) {
138138 if (
139139 ! err ?. constraint ||
140- err . constraint !== 'uix_memberIdentities_platform_value_type_verified' ||
141- ! err . detail
140+ err . constraint !== 'uix_memberIdentities_platform_value_type_verified'
142141 ) {
143142 throw err
144143 }
145- const match = ( err . detail as string ) . match ( / \( p l a t f o r m , v a l u e , t y p e \) = \( ( .* ?) \) / )
146- if ( ! match ) throw err
147- const [ platform , value , type ] = match [ 1 ] . split ( ',' ) . map ( ( s : string ) => s . trim ( ) )
148-
149- const owner = await findMemberIdByVerifiedIdentity (
150- this . pgQx ,
151- platform ,
152- value ,
153- type as MemberIdentityType ,
154- )
155144
156- if ( owner && owner !== memberId ) {
157- if ( ! attemptMerge ) {
158- return owner
145+ const verifiedIncoming = identities . filter ( ( i ) => i . verified )
146+ if ( verifiedIncoming . length === 0 ) throw err
147+
148+ // Use the structured identities array to find the owner — avoids fragile Postgres
149+ // Detail text parsing (format not stable; breaks if value contains a comma).
150+ const owners = await findMembersByIdentities ( this . pgQx , verifiedIncoming , undefined , true )
151+
152+ // Map keys are `${platform}:${type}:${value}` (from db rows). Match case-insensitively.
153+ let owner : string | undefined
154+ outer: for ( const id of verifiedIncoming ) {
155+ for ( const [ key , ownerId ] of owners ) {
156+ const sep1 = key . indexOf ( ':' )
157+ const sep2 = key . indexOf ( ':' , sep1 + 1 )
158+ if ( sep1 < 0 || sep2 < 0 ) continue
159+ if (
160+ key . slice ( 0 , sep1 ) === id . platform &&
161+ key . slice ( sep1 + 1 , sep2 ) === id . type &&
162+ key . slice ( sep2 + 1 ) . toLowerCase ( ) === id . value . toLowerCase ( ) &&
163+ ownerId !== memberId
164+ ) {
165+ owner = ownerId
166+ break outer
167+ }
159168 }
169+ }
160170
161- let merged : boolean
162- try {
163- merged = await mergeIfAllowed ( this . pgQx , this . temporal , this . log , owner , memberId )
164- } catch ( mergeErr ) {
165- // Re-throw the original constraint error, not the merge error. If we let the merge
166- // error propagate, handleMemberIdentityError's checkForIdentityConstraint would
167- // return false and no metadata would be stored in integration.results.error.
168- // Re-throwing the constraint error lets handleMemberIdentityError retry the merge.
169- this . log . warn (
170- mergeErr ,
171- { memberId, owner, platform, value, type } ,
172- 'merge threw during identity conflict — re-throwing constraint error for retry' ,
173- )
174- throw err
175- }
176- if ( merged ) return owner
171+ if ( ! owner ) throw err
172+
173+ if ( ! attemptMerge ) {
174+ return owner
175+ }
176+
177+ let merged : boolean
178+ try {
179+ merged = await mergeIfAllowed ( this . pgQx , this . temporal , this . log , owner , memberId )
180+ } catch ( mergeErr ) {
181+ // Re-throw the original constraint error, not the merge error. If we let the merge
182+ // error propagate, handleMemberIdentityError's checkForIdentityConstraint would
183+ // return false and no metadata would be stored in integration.results.error.
184+ // Re-throwing the constraint error lets handleMemberIdentityError retry the merge.
185+ this . log . warn (
186+ mergeErr ,
187+ { memberId, owner } ,
188+ 'merge threw during identity conflict — re-throwing constraint error for retry' ,
189+ )
190+ throw err
177191 }
192+ if ( merged ) return owner
178193
179194 // noMerge, owner not found, or stale prefetch (owner === memberId):
180195 // Re-throw original constraint DatabaseError. handleMemberIdentityError will call
0 commit comments