@@ -79,6 +79,9 @@ export const createUserQueries = (pool: CommonQueryMethods) => {
7979 where lower(${ fields . primaryEmail } )=lower(${ email } )
8080 ` ) ;
8181
82+ /**
83+ * Find user by phone with exact match.
84+ */
8285 const findUserByPhone = async ( phone : string ) =>
8386 pool . maybeOne < User > ( sql `
8487 select ${ sql . join ( Object . values ( fields ) , sql `,` ) }
@@ -195,7 +198,7 @@ export const createUserQueries = (pool: CommonQueryMethods) => {
195198 ` ) ;
196199
197200 /**
198- * Find user by phone with exact match.
201+ * Checks if a user exists in the database with an exact match on their phone number .
199202 */
200203 const hasUserWithPhone = async ( phone : string , excludeUserId ?: string ) =>
201204 pool . exists ( sql `
@@ -205,6 +208,51 @@ export const createUserQueries = (pool: CommonQueryMethods) => {
205208 ${ conditionalSql ( excludeUserId , ( id ) => sql `and ${ fields . id } <>${ id } ` ) }
206209 ` ) ;
207210
211+ /**
212+ * Checks if a user exists in the database with a phone number that matches the provided
213+ * number in either normalized format or with a leading '0'.
214+ *
215+ * @remarks
216+ * This function normalizes the input phone number to account for variations in formatting.
217+ * It checks for the existence of a user with the same phone number in two formats:
218+ * - Standard international format (e.g., 61412345678)
219+ * - International format with a leading '0' before the local number (e.g., 610412345678)
220+ *
221+ * If the provided phone number is not a valid international format, it falls back to checking
222+ * for an exact match using the `hasUserWithPhone` function.
223+ *
224+ * @param phone - The phone number to check for user existence.
225+ * @param excludeUserId - (Optional) If provided, excludes the user with this ID from the search,
226+ * allowing for updates without false positives.
227+ *
228+ * @example
229+ * // Database contains: 610412345678
230+ * hasUserWithNormalizedPhone(61412345678); // returns: true
231+ *
232+ * @example
233+ * // Database contains: 61412345678
234+ * hasUserWithNormalizedPhone(610412345678); // returns: true
235+ */
236+ const hasUserWithNormalizedPhone = async ( phone : string , excludeUserId ?: string ) => {
237+ const phoneNumberParser = new PhoneNumberParser ( phone ) ;
238+
239+ const { internationalNumber, internationalNumberWithLeadingZero, isValid } = phoneNumberParser ;
240+
241+ // If the phone number is not a valid international phone number, find user with exact match.
242+ if ( ! isValid || ! internationalNumber || ! internationalNumberWithLeadingZero ) {
243+ return hasUserWithPhone ( phone , excludeUserId ) ;
244+ }
245+
246+ // Check if the user exists with any of the two formats.
247+ return pool . exists ( sql `
248+ select ${ fields . primaryPhone }
249+ from ${ table }
250+ where (${ fields . primaryPhone } =${ internationalNumber }
251+ or ${ fields . primaryPhone } =${ internationalNumberWithLeadingZero } )
252+ ${ conditionalSql ( excludeUserId , ( id ) => sql `and ${ fields . id } <>${ id } ` ) }
253+ ` ) ;
254+ } ;
255+
208256 const hasUserWithIdentity = async ( target : string , userId : string , excludeUserId ?: string ) =>
209257 pool . exists (
210258 sql `
@@ -341,6 +389,7 @@ export const createUserQueries = (pool: CommonQueryMethods) => {
341389 hasUserWithId,
342390 hasUserWithEmail,
343391 hasUserWithPhone,
392+ hasUserWithNormalizedPhone,
344393 hasUserWithIdentity,
345394 countUsers,
346395 findUsers,
0 commit comments