-
Notifications
You must be signed in to change notification settings - Fork 47
feat: add KYC data migration script from CSV to Supabase #304
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||||||
| # KYC Data Migration from CSV | ||||||||||
|
|
||||||||||
| This script migrates user KYC data from a local CSV file into the Supabase `user_kyc_profiles` table. | ||||||||||
|
|
||||||||||
| ## Prerequisites | ||||||||||
|
|
||||||||||
| ### 1. Install Dependencies | ||||||||||
|
|
||||||||||
| If you haven't already, install the required packages: | ||||||||||
| ```bash | ||||||||||
| npm install @supabase/supabase-js dotenv ts-node typescript @types/node csv-parse | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### 2. Environment Variables | ||||||||||
|
|
||||||||||
| Ensure your `.env` file contains the following Supabase credentials: | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| # Supabase | ||||||||||
| NEXT_PUBLIC_SUPABASE_URL=your_supabase_url | ||||||||||
| SUPABASE_SERVICE_ROLE_KEY=your_service_role_key | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### 3. CSV Data File | ||||||||||
|
|
||||||||||
| Place a CSV file named `kyc-data.csv` inside the `scripts/` directory. The script expects the file to have a header row with column names that match the `CsvRow` interface in the script. | ||||||||||
|
|
||||||||||
| **Expected CSV Columns:** | ||||||||||
| - `user_id` (maps to `wallet_address`) | ||||||||||
| - `id_type` | ||||||||||
| - `country` | ||||||||||
|
|
||||||||||
| ## Usage | ||||||||||
|
|
||||||||||
| ### Dry Run (Recommended First) | ||||||||||
|
|
||||||||||
| To preview the data that will be inserted without writing anything to the database, run: | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| npx ts-node scripts/migrate-kyc-data.ts --dry-run | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| This command will: | ||||||||||
| - Read and parse `kyc-data.csv`. | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update CSV filename references in usage instructions. These lines also reference Also applies to: 58-58 🤖 Prompt for AI Agents |
||||||||||
| - Transform the first 5 records into the database format. | ||||||||||
| - Print the transformed data to the console. | ||||||||||
| - **It will NOT write any data to Supabase.** | ||||||||||
|
|
||||||||||
| ### Full Migration | ||||||||||
|
|
||||||||||
| Once you have verified that the dry run output is correct, you can perform the full migration: | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| npx ts-node scripts/migrate-kyc-data.ts | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| This command will: | ||||||||||
| 1. Read all records from `kyc-data.csv`. | ||||||||||
| 2. Transform each record to match the `user_kyc_profiles` schema. | ||||||||||
| 3. Upsert each record into the Supabase table, using `wallet_address` to handle conflicts. | ||||||||||
|
|
||||||||||
| ## How It Works | ||||||||||
|
|
||||||||||
| ### 1. Extraction | ||||||||||
| - The script reads the `kyc-data.csv` file from the `scripts/` directory. | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update CSV filename reference. Another reference to 🤖 Prompt for AI Agents |
||||||||||
| - It uses the `csv-parse` library to parse the file into an array of JavaScript objects. | ||||||||||
|
|
||||||||||
| ### 2. Transformation | ||||||||||
| For each row in the CSV: | ||||||||||
| - It maps the CSV columns to the fields in the `user_kyc_profiles` table. | ||||||||||
| - `user_id` from the CSV is used as the `wallet_address`. | ||||||||||
| - It defaults to `tier: 2` for all migrated users. | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove incorrect tier field documentation. The documentation states "It defaults to Remove or correct this statement: -For each row in the CSV:
-- It maps the CSV columns to the fields in the `user_kyc_profiles` table.
-- `user_id` from the CSV is used as the `wallet_address`.
-- It defaults to `tier: 2` for all migrated users.
+For each row in the CSV:
+- It maps the CSV columns to the fields in the `user_kyc_profiles` table.
+- `user_id` from the CSV is used as the `wallet_address`.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| ### 3. Loading | ||||||||||
| - The script connects to your Supabase instance using the service role key. | ||||||||||
| - It iterates through the transformed records and uses `supabase.from('user_kyc_profiles').upsert(...)` to insert or update each one. | ||||||||||
| - The `onConflict: 'wallet_address'` option ensures that existing records are updated, preventing duplicates. | ||||||||||
|
|
||||||||||
| ## Data Mapping | ||||||||||
|
|
||||||||||
| | CSV Column | New Schema Field | Notes | | ||||||||||
| |----------------|--------------------|--------------------------------------------| | ||||||||||
| | `user_id` | `wallet_address` | Primary key, lowercased for consistency. | | ||||||||||
| | `id_type` | `id_type` | | | ||||||||||
| | `country` | `id_country` | | | ||||||||||
| | `smile_job_id` | `smile_job_id` | | | ||||||||||
| | `verified_at` | `verified_at` | Also sets `verified` to `true`. | | ||||||||||
| | - | `tier` | Hardcoded to `2` for all records. | | ||||||||||
|
Comment on lines
+86
to
+88
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the data mapping table. The data mapping table contains inaccuracies:
Remove these entries or update the script to include them: | CSV Column | New Schema Field | Notes |
|----------------|--------------------|--------------------------------------------|
| `user_id` | `wallet_address` | Primary key, lowercased for consistency. |
| `id_type` | `id_type` | |
| `country` | `id_country` | |
-| `smile_job_id` | `smile_job_id` | |
| `verified_at` | `verified_at` | Also sets `verified` to `true`. |
-| - | `tier` | Hardcoded to `2` for all records. |📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| ## Troubleshooting | ||||||||||
|
|
||||||||||
| ### `CSV file not found` | ||||||||||
| **Issue**: The script throws an error `CSV file not found at: <path>`. | ||||||||||
| **Solution**: Make sure your CSV file is named exactly `kyc-data.csv` and is located in the `/Users/prof/Documents/paycrest/noblocks/scripts/` directory. | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove hardcoded absolute path. The troubleshooting section contains a hardcoded absolute path specific to a developer's machine: Use a generic path reference: -**Solution**: Make sure your CSV file is named exactly `kyc-data.csv` and is located in the `/Users/prof/Documents/paycrest/noblocks/scripts/` directory.
+**Solution**: Make sure your CSV file is named exactly `kyc-export.csv` and is located in the `scripts/` directory.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| ### `Missing Supabase credentials` | ||||||||||
| **Issue**: The script throws an error about missing credentials. | ||||||||||
| **Solution**: Ensure your `.env` file is in the root of the `noblocks` project and contains the correct `NEXT_PUBLIC_SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY`. | ||||||||||
|
|
||||||||||
| ### Data not appearing as expected | ||||||||||
| **Issue**: The data in Supabase doesn't look right. | ||||||||||
| **Solution**: | ||||||||||
| 1. Run the script with `--dry-run` and inspect the JSON output in your console. | ||||||||||
| 2. Check that the column headers in your `kyc-data.csv` file exactly match the expected names (e.g., `user_id`, `phone_number`). | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove incorrect field reference. Line 104 mentions Remove this incorrect reference: **Solution**:
1. Run the script with `--dry-run` and inspect the JSON output in your console.
-2. Check that the column headers in your `kyc-data.csv` file exactly match the expected names (e.g., `user_id`, `phone_number`).
+2. Check that the column headers in your CSV file exactly match the expected names (e.g., `Job ID`, `User ID`, `Country`, `ID Type`, `Result`).
3. Verify the data formats in the CSV (e.g., dates in `verified_at` are valid).📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| 3. Verify the data formats in the CSV (e.g., dates in `verified_at` are valid). | ||||||||||
|
|
||||||||||
| ## Rollback | ||||||||||
|
|
||||||||||
| If you need to undo the migration, you can run a SQL query in the Supabase SQL Editor. Be very careful with this operation. | ||||||||||
|
|
||||||||||
| ```sql | ||||||||||
| -- Example: Delete records that were created or updated by the script. | ||||||||||
| -- You might need to adjust the timestamp to match your migration time. | ||||||||||
| DELETE FROM public.user_kyc_profiles WHERE updated_at >= '2025-12-02 00:00:00+00'; | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| It is safer to identify the migrated records by a set of `wallet_address` values if possible. | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,172 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Migrate minimal KYC fields from a CSV export to Supabase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - Reads CSV with headers: Job ID, User ID, Country, ID Type, Result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - Filters rows where Result === "Approved" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - Upserts into public.user_kyc_profiles: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - wallet_address ← User ID (lowercased) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - id_country ← Country | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - id_type ← ID Type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - verified=true, verified_at=now (optional; remove if not desired) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Usage: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * npx ts-node scripts/migrate-kyc-data.ts --dry-run | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * npx ts-node scripts/migrate-kyc-data.ts --csv ./kyc-export.csv | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createClient } from '@supabase/supabase-js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as dotenv from 'dotenv'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import fs from 'fs'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import path from 'path'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { fileURLToPath } from 'url'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { parse } from 'csv-parse/sync'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dotenv.config(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ESM-safe path resolution | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const __filename = fileURLToPath(import.meta.url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const __dirname = path.dirname(__filename); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Env | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!SUPABASE_URL || !SUPABASE_SERVICE_KEY) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Missing Supabase credentials in .env (NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // CLI: allow overriding the CSV path via --csv | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const csvArgIndex = process.argv.findIndex((a) => a === '--csv'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const CSV_FILE_PATH = csvArgIndex >= 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? path.resolve(process.argv[csvArgIndex + 1]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : path.join(__dirname, 'kyc-export.csv'); // default name next to script | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface CsvRowRaw { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Job ID'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'User ID'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'SDK'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Date'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Timestamp'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Job Time'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Product'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Job Type'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Country'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'ID Type'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Result'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Message'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'SmartCheck User'?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type CsvRow = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| job_id: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| country?: string | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id_type?: string | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Read + normalize CSV | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function readCsv(): CsvRow[] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(CSV_FILE_PATH)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error(`CSV file not found at: ${CSV_FILE_PATH}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(` Reading data from ${CSV_FILE_PATH}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const content = fs.readFileSync(CSV_FILE_PATH); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const raw = parse(content, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| columns: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_empty_lines: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trim: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) as CsvRowRaw[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Found ${raw.length} records in CSV file.`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rows: CsvRow[] = raw.map((r) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| job_id: (r['Job ID'] || '').trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id: (r['User ID'] || '').trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| country: r['Country'] ? r['Country'].trim() : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id_type: r['ID Type'] ? r['ID Type'].trim() : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result: (r['Result'] || '').trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const valid = rows.filter((r) => r.job_id && r.user_id && r.result); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const skipped = rows.length - valid.length; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (skipped > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn(` Skipped ${skipped} rows missing Job ID, User ID, or Result`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return valid; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function buildRow(r: CsvRow) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isApproved = r.result === 'Approved'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const nowISO = new Date().toISOString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wallet_address: r.user_id.toLowerCase(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id_country: r.country || null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id_type: r.id_type || null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| platform: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: 'id', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| identifier: 'smile_id', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reference: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+111
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use job_id for platform reference instead of empty string. The Apply this diff: platform: [
{
type: 'id',
identifier: 'smile_id',
- reference: '',
+ reference: r.job_id,
}
],📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verified: isApproved, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verified_at: isApproved ? nowISO : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "migrate-kyc-data.ts"Repository: paycrest/noblocks Length of output: 90 🏁 Script executed: head -200 scripts/migrate-kyc-data.ts | cat -nRepository: paycrest/noblocks Length of output: 6143 🏁 Script executed: wc -l scripts/migrate-kyc-data.tsRepository: paycrest/noblocks Length of output: 92 Add historical verification timestamp from CSV instead of current migration time. The CSV export contains a To preserve historical accuracy: type CsvRow = {
job_id: string;
user_id: string;
country?: string | null;
id_type?: string | null;
result: string;
+ timestamp?: string | null;
};
const rows: CsvRow[] = raw.map((r) => ({
job_id: (r['Job ID'] || '').trim(),
user_id: (r['User ID'] || '').trim(),
country: r['Country'] ? r['Country'].trim() : null,
id_type: r['ID Type'] ? r['ID Type'].trim() : null,
result: (r['Result'] || '').trim(),
+ timestamp: r['Timestamp'] ? r['Timestamp'].trim() : null,
}));
function buildRow(r: CsvRow) {
const isApproved = r.result === 'Approved';
+ const verifiedAt = isApproved && r.timestamp
+ ? new Date(r.timestamp).toISOString()
+ : (isApproved ? new Date().toISOString() : null);
return {
wallet_address: r.user_id.toLowerCase(),
id_country: r.country || null,
id_type: r.id_type || null,
platform: [
{
type: 'id',
identifier: 'smile_id',
reference: '',
}
],
verified: isApproved,
- verified_at: isApproved ? nowISO : null,
+ verified_at: verifiedAt,
- updated_at: nowISO,
+ updated_at: new Date().toISOString(),
};
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updated_at: nowISO, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Upsert — conflict target: wallet_address (PK) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function upsertRows(rows: any[]) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Replace The function parameter uses Apply this diff: +type KycProfileRow = {
+ wallet_address: string;
+ id_country: string | null;
+ id_type: string | null;
+ platform: Array<{ type: string; identifier: string; reference: string }>;
+ verified: boolean;
+ verified_at: string | null;
+ updated_at: string;
+};
+
-async function upsertRows(rows: any[]) {
+async function upsertRows(rows: KycProfileRow[]) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`\nUpserting ${rows.length} records into public.user_kyc_profiles...`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let ok = 0, failed = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const row of rows) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { error } = await supabase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from('user_kyc_profiles') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .upsert(row, { onConflict: 'wallet_address' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`❌ ${row.wallet_address}: ${error.message}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failed++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`✅ ${row.wallet_address}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`\n Summary: OK=${ok}, Failed=${failed}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function main() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isDryRun = process.argv.includes('--dry-run'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(isDryRun ? 'Running in DRY RUN mode.' : 'Running in LIVE mode.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const all = readCsv(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const approved = all.filter((r) => r.result === 'Approved'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`✅ Approved rows: ${approved.length}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rows = approved.map(buildRow); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isDryRun) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('--- Dry Run Output (first 5 rows) ---'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(JSON.stringify(rows.slice(0, 5), null, 2)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('-------------------------------------'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('No data will be written to the database in dry run mode.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await upsertRows(rows); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('🎉 Migration script finished.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(' An error occurred:', err.message || err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| main(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix CSV filename inconsistency.
The documentation states the CSV file should be named
kyc-data.csv, but the actual script defaults tokyc-export.csv(line 43 in migrate-kyc-data.ts). This mismatch will cause confusion when users follow the documentation.Update the filename to match the script's default:
📝 Committable suggestion
🤖 Prompt for AI Agents