Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add migration script for phone number normalisation #882

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,23 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [7.0.12] - 2023-11-16

- Adds Phone Number normalisation
In this release, the core API routes have been updated to incorporate phone number normalization before processing. Consequently, existing entries in the database also need to undergo normalization. To facilitate this, we have included a migration script to normalize phone numbers for all the existing entries.
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved

**NOTE**: You can skip the migration if you are not using passwordless via phone number.

### Migration steps

rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
1. Ensure that the core is already upgraded to version 7.0.12 (CDI version 4.0)
2. Run the migration script

Make sure your Node.js version is 16 or above to run the script. Locate the migration script at `supertokens-core/migration_scripts/to_version_7_1_12/index.js`. Modify the script by updating the `DB_HOST`, `DB_USER`, `DB_PASSWORD`, and `DB_NAME` variables with the correct values. Subsequently, run the following commands to initiate the script:
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved

```bash
$ git clone https://github.com/supertokens/supertokens-core.git
$ cd supertokens-core/migration_scripts/to_version_7_1_12
$ npm install
$ npm start
```

## [7.0.11] - 2023-11-10

Expand Down
1 change: 1 addition & 0 deletions migration_scripts/to_version_7_1_12/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
110 changes: 110 additions & 0 deletions migration_scripts/to_version_7_1_12/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

const libphonenumber = require('libphonenumber-js/max');

// Update the following credentials before running the script
const DB_HOST = "";
const DB_USER = "";
const DB_PASSWORD = "";
const DB_NAME = "";
const CLIENT = ""; // Use "pg" for PostgreSQL and "mysql2" for MySQL DB

if (!DB_HOST || !CLIENT) {
console.error('Please update the DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE and CLIENT variables before running the script.');
return;
}

const knex = require('knex')({
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
client: CLIENT,
connection: {
host: DB_HOST,
user: DB_USER,
password: DB_PASSWORD,
database: DB_NAME,
},
pool: { min: 0, max: 5 }
});

function getUpdatePromise(table, entry, normalizedPhoneNumber) {
if (table === 'passwordless_devices') {
return knex.raw(`UPDATE ${table} SET phone_number = ? WHERE app_id = ? AND tenant_id = ? AND device_id_hash = ?`, [normalizedPhoneNumber, entry.app_id, entry.tenant_id, entry.device_id_hash]);
} else if (table === 'passwordless_users') {
// Since passwordless_users and passwordless_user_to_tenant are consistent. We can update both tables at the same time. For consistency, we will use a transaction.
return knex.transaction(async trx => {
await trx.raw(`UPDATE passwordless_users SET phone_number = ? WHERE app_id = ? AND user_id = ?`, [normalizedPhoneNumber, entry.app_id, entry.user_id]);
await trx.raw(`UPDATE passwordless_user_to_tenant SET phone_number = ? WHERE app_id = ? AND user_id = ?`, [normalizedPhoneNumber, entry.app_id, entry.user_id]);
});
}
}

function getNormalizedPhoneNumber(phoneNumber) {
try {
return libphonenumber.parsePhoneNumber(phoneNumber, { extract: false }).format('E.164');
} catch (error) {
return null;
}
}

async function updatePhoneNumbers(table) {
const batchSize = 1000;
let offset = 0;

try {
while (true) {
const entries = await knex.raw(`SELECT * FROM ${table} WHERE phone_number is NOT NULL LIMIT ${batchSize} OFFSET ${offset}`);
const rows = entries.rows ? entries.rows : entries[0];
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved

const batchUpdates = [];

for (const entry of rows) {
const currentPhoneNumber = entry.phone_number;
const normalizedPhoneNumber = getNormalizedPhoneNumber(currentPhoneNumber);

if (normalizedPhoneNumber && normalizedPhoneNumber !== currentPhoneNumber) {
const updatePromise = getUpdatePromise(table, entry, normalizedPhoneNumber);
batchUpdates.push(updatePromise);
}
}

await Promise.all(batchUpdates);

offset += rows.length;

console.log(`Processed ${offset} rows for table ${table}`);
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved

if (rows.length < batchSize) {
break;
}
}
} catch (error) {
console.error(`Error normalising phone numbers for table ${table}:`, error.message);
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
}
}

async function runScript() {
const tables = ['passwordless_users', 'passwordless_devices', 'passwordless_user_to_tenant'];
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved

for (const table of tables) {
await updatePhoneNumbers(table);
}

console.log('Finished normalising phone numbers!');

knex.destroy();
}

runScript();
Loading
Loading