mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-25 02:00:10 +02:00
Merge pull request #28470 from overleaf/ls-script-to-migration-user-id-metadata
Add scripts to migrate userId metadata GitOrigin-RevId: 2365afd7a6772f147d2a3eb2f3e033cab3a4e7dd
This commit is contained in:
248
services/web/scripts/stripe/add_user_id_to_stripe_customer.mjs
Normal file
248
services/web/scripts/stripe/add_user_id_to_stripe_customer.mjs
Normal file
@@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script iterates through all Stripe subscriptions, checks if they have metadata adminUserId,
|
||||
* and sets it to customer metadata "userId" if present.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us [options]
|
||||
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=uk [options]
|
||||
*
|
||||
* Options:
|
||||
* --region=us|uk Required. Stripe region to process (us or uk)
|
||||
* --commit Actually perform the updates (default: dry-run mode)
|
||||
* --verbose Enable verbose logging
|
||||
* --limit=N Limit processing to N subscriptions (for testing)
|
||||
*
|
||||
* Examples:
|
||||
* # Dry run for US region with verbose output
|
||||
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us --verbose
|
||||
*
|
||||
* # Commit changes for UK region
|
||||
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=uk --commit
|
||||
*
|
||||
* # Test with limited subscriptions
|
||||
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us --limit=10 --verbose
|
||||
*/
|
||||
|
||||
import minimist from 'minimist'
|
||||
import { z } from '../../app/src/infrastructure/Validation.js'
|
||||
import { scriptRunner } from '../lib/ScriptRunner.mjs'
|
||||
import {
|
||||
getRegionClient,
|
||||
CUSTOMER_SEGMENT_MAPPING,
|
||||
} from '../../modules/subscriptions/app/src/StripeClient.mjs'
|
||||
|
||||
const paramsSchema = z.object({
|
||||
region: z.enum(['us', 'uk']),
|
||||
commit: z.boolean().default(false),
|
||||
verbose: z.boolean().default(false),
|
||||
limit: z.number().int().min(1).optional(),
|
||||
})
|
||||
|
||||
let processedCount = 0
|
||||
let updatedCount = 0
|
||||
let errorCount = 0
|
||||
|
||||
/**
|
||||
* Sleep function to respect Stripe rate limits (100 requests per second)
|
||||
* We'll be conservative and sleep for 50ms between requests to stay well under the limit
|
||||
*/
|
||||
async function rateLimitSleep() {
|
||||
return new Promise(resolve => setTimeout(resolve, 50))
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single subscription and update customer metadata if needed
|
||||
*/
|
||||
async function processSubscription(
|
||||
subscription,
|
||||
stripeClient,
|
||||
commit,
|
||||
verbose
|
||||
) {
|
||||
try {
|
||||
processedCount++
|
||||
|
||||
// Check if subscription has adminUserId metadata
|
||||
const adminUserId = subscription.metadata?.adminUserId
|
||||
|
||||
if (verbose) {
|
||||
console.info(
|
||||
`Processing subscription ${subscription.id} (customer: ${subscription.customer.id}) - adminUserId: ${adminUserId || 'none'}`
|
||||
)
|
||||
}
|
||||
|
||||
if (!adminUserId) {
|
||||
// No adminUserId to migrate
|
||||
return
|
||||
}
|
||||
|
||||
// Get customer details to check current metadata
|
||||
const customer = subscription.customer
|
||||
|
||||
if (customer.metadata?.userId === adminUserId) {
|
||||
if (verbose) {
|
||||
console.info(
|
||||
`Customer ${customer.id} already has userId=${adminUserId}, skipping`
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (customer.metadata?.userId && customer.metadata.userId !== adminUserId) {
|
||||
console.warn(
|
||||
`Customer ${customer.id} has existing userId=${customer.metadata.userId} which differs from adminUserId=${adminUserId}, skipping to avoid overwrite`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (commit) {
|
||||
// Update customer metadata using the StripeClient method
|
||||
await stripeClient.updateCustomerMetadata(customer.id, {
|
||||
...customer.metadata,
|
||||
userId: adminUserId,
|
||||
segment: CUSTOMER_SEGMENT_MAPPING.B2C,
|
||||
})
|
||||
|
||||
console.info(
|
||||
`Updated customer ${customer.id} metadata: userId=${adminUserId}`
|
||||
)
|
||||
} else {
|
||||
console.info(
|
||||
`DRY RUN: Would update customer ${customer.id} metadata: userId=${adminUserId}`
|
||||
)
|
||||
}
|
||||
|
||||
updatedCount++
|
||||
} catch (error) {
|
||||
errorCount++
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
// Respect rate limits
|
||||
await rateLimitSleep()
|
||||
}
|
||||
|
||||
/**
|
||||
* Main script function
|
||||
*/
|
||||
async function main(trackProgress) {
|
||||
const parseResult = paramsSchema.safeParse(
|
||||
minimist(process.argv.slice(2), {
|
||||
boolean: ['commit', 'verbose'],
|
||||
string: ['region'],
|
||||
number: ['limit'],
|
||||
})
|
||||
)
|
||||
|
||||
if (!parseResult.success) {
|
||||
throw new Error(`Invalid parameters: ${parseResult.error.message}`)
|
||||
}
|
||||
|
||||
const { region, commit, verbose, limit } = parseResult.data
|
||||
|
||||
const mode = commit ? 'COMMIT MODE' : 'DRY RUN MODE'
|
||||
await trackProgress(
|
||||
`Starting script in ${mode} for Stripe ${region.toUpperCase()} region`
|
||||
)
|
||||
|
||||
if (limit) {
|
||||
await trackProgress(`Processing limited to ${limit} subscriptions`)
|
||||
}
|
||||
|
||||
// Get Stripe client for the specified region
|
||||
const stripeClient = getRegionClient(region)
|
||||
|
||||
// Reset counters
|
||||
processedCount = 0
|
||||
updatedCount = 0
|
||||
errorCount = 0
|
||||
|
||||
await trackProgress('Starting to iterate through Stripe subscriptions...')
|
||||
|
||||
const listParams = {
|
||||
limit: 100, // Stripe's maximum limit per request
|
||||
expand: ['data.customer'], // Expand customer data to reduce additional API calls
|
||||
}
|
||||
|
||||
let hasMore = true
|
||||
let startingAfter = null
|
||||
let totalProcessed = 0
|
||||
|
||||
while (hasMore) {
|
||||
const params = { ...listParams }
|
||||
if (startingAfter) {
|
||||
params.starting_after = startingAfter
|
||||
}
|
||||
|
||||
// Get batch of subscriptions
|
||||
const subscriptions = await stripeClient.stripe.subscriptions.list(params)
|
||||
|
||||
await trackProgress(
|
||||
`Retrieved ${subscriptions.data.length} subscriptions (total processed so far: ${totalProcessed})`
|
||||
)
|
||||
|
||||
// Process each subscription in the batch
|
||||
for (const subscription of subscriptions.data) {
|
||||
await processSubscription(subscription, stripeClient, commit, verbose)
|
||||
|
||||
totalProcessed++
|
||||
|
||||
// Check if we've hit the limit
|
||||
if (limit && totalProcessed >= limit) {
|
||||
await trackProgress(`Reached limit of ${limit} subscriptions, stopping`)
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
|
||||
// Progress update every 50 subscriptions
|
||||
if (totalProcessed % 50 === 0) {
|
||||
await trackProgress(
|
||||
`Progress: ${totalProcessed} processed, ${updatedCount} customers updated, ${errorCount} errors`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are more subscriptions to process
|
||||
hasMore = hasMore && subscriptions.has_more
|
||||
if (hasMore && subscriptions.data.length > 0) {
|
||||
startingAfter = subscriptions.data[subscriptions.data.length - 1].id
|
||||
}
|
||||
|
||||
// Rate limit between batch requests
|
||||
await rateLimitSleep()
|
||||
}
|
||||
|
||||
// Final summary
|
||||
await trackProgress('FINAL SUMMARY:')
|
||||
await trackProgress(` Total subscriptions processed: ${processedCount}`)
|
||||
await trackProgress(
|
||||
` Customers ${commit ? 'updated' : 'would be updated'}: ${updatedCount}`
|
||||
)
|
||||
await trackProgress(` Errors encountered: ${errorCount}`)
|
||||
|
||||
if (!commit && updatedCount > 0) {
|
||||
await trackProgress('')
|
||||
await trackProgress(
|
||||
'To actually perform the updates, run the script with --commit flag'
|
||||
)
|
||||
}
|
||||
|
||||
if (errorCount > 0) {
|
||||
await trackProgress(
|
||||
'Some errors were encountered. Check the logs above for details.'
|
||||
)
|
||||
}
|
||||
|
||||
await trackProgress(`Script completed successfully in ${mode}`)
|
||||
}
|
||||
|
||||
// Execute the script using the runner
|
||||
try {
|
||||
await scriptRunner(main)
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error('Script failed:', error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script iterates through all Stripe subscriptions and removes the adminUserId metadata
|
||||
* from subscription objects that have it.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=us [options]
|
||||
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=uk [options]
|
||||
*
|
||||
* Options:
|
||||
* --region=us|uk Required. Stripe region to process (us or uk)
|
||||
* --commit Actually perform the updates (default: dry-run mode)
|
||||
* --verbose Enable verbose logging
|
||||
* --limit=N Limit processing to N subscriptions (for testing)
|
||||
*
|
||||
* Examples:
|
||||
* # Dry run for US region with verbose output
|
||||
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=us --verbose
|
||||
*
|
||||
* # Commit changes for UK region
|
||||
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=uk --commit
|
||||
*
|
||||
* # Test with limited subscriptions
|
||||
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=us --limit=10 --verbose
|
||||
*/
|
||||
|
||||
import minimist from 'minimist'
|
||||
import { z } from '../../app/src/infrastructure/Validation.js'
|
||||
import { scriptRunner } from '../lib/ScriptRunner.mjs'
|
||||
import { getRegionClient } from '../../modules/subscriptions/app/src/StripeClient.mjs'
|
||||
|
||||
const paramsSchema = z.object({
|
||||
region: z.enum(['us', 'uk']),
|
||||
commit: z.boolean().default(false),
|
||||
verbose: z.boolean().default(false),
|
||||
limit: z.number().int().min(1).optional(),
|
||||
})
|
||||
|
||||
let processedCount = 0
|
||||
let updatedCount = 0
|
||||
let errorCount = 0
|
||||
|
||||
/**
|
||||
* Sleep function to respect Stripe rate limits (100 requests per second)
|
||||
* We'll be conservative and sleep for 50ms between requests to stay well under the limit
|
||||
*/
|
||||
async function rateLimitSleep() {
|
||||
return new Promise(resolve => setTimeout(resolve, 50))
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single subscription and remove adminUserId metadata if present
|
||||
*/
|
||||
async function processSubscription(
|
||||
subscription,
|
||||
stripeClient,
|
||||
commit,
|
||||
verbose
|
||||
) {
|
||||
try {
|
||||
processedCount++
|
||||
|
||||
// Check if subscription has adminUserId metadata
|
||||
const adminUserId = subscription.metadata?.adminUserId
|
||||
|
||||
if (verbose) {
|
||||
console.info(
|
||||
`Processing subscription ${subscription.id} - adminUserId: ${adminUserId || 'none'}`
|
||||
)
|
||||
}
|
||||
|
||||
if (!adminUserId) {
|
||||
// No adminUserId to remove
|
||||
return
|
||||
}
|
||||
|
||||
if (commit) {
|
||||
// Create a new metadata object that will remove adminUserId
|
||||
const updatedMetadata = { ...subscription.metadata }
|
||||
updatedMetadata.adminUserId = ''
|
||||
|
||||
// Update subscription metadata using Stripe API directly
|
||||
await stripeClient.stripe.subscriptions.update(subscription.id, {
|
||||
metadata: updatedMetadata,
|
||||
})
|
||||
|
||||
console.info(
|
||||
`Removed adminUserId metadata from subscription ${subscription.id}`
|
||||
)
|
||||
} else {
|
||||
console.info(
|
||||
`DRY RUN: Would remove adminUserId metadata from subscription ${subscription.id}`
|
||||
)
|
||||
}
|
||||
|
||||
updatedCount++
|
||||
} catch (error) {
|
||||
errorCount++
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
// Respect rate limits
|
||||
await rateLimitSleep()
|
||||
}
|
||||
|
||||
/**
|
||||
* Main script function
|
||||
*/
|
||||
async function main(trackProgress) {
|
||||
const parseResult = paramsSchema.safeParse(
|
||||
minimist(process.argv.slice(2), {
|
||||
boolean: ['commit', 'verbose'],
|
||||
string: ['region'],
|
||||
number: ['limit'],
|
||||
})
|
||||
)
|
||||
|
||||
if (!parseResult.success) {
|
||||
throw new Error(`Invalid parameters: ${parseResult.error.message}`)
|
||||
}
|
||||
|
||||
const { region, commit, verbose, limit } = parseResult.data
|
||||
|
||||
const mode = commit ? 'COMMIT MODE' : 'DRY RUN MODE'
|
||||
await trackProgress(
|
||||
`Starting script in ${mode} for Stripe ${region.toUpperCase()} region`
|
||||
)
|
||||
|
||||
if (limit) {
|
||||
await trackProgress(`Processing limited to ${limit} subscriptions`)
|
||||
}
|
||||
|
||||
// Get Stripe client for the specified region
|
||||
const stripeClient = getRegionClient(region)
|
||||
|
||||
// Reset counters
|
||||
processedCount = 0
|
||||
updatedCount = 0
|
||||
errorCount = 0
|
||||
|
||||
await trackProgress('Starting to iterate through Stripe subscriptions...')
|
||||
|
||||
const listParams = {
|
||||
limit: 100, // Stripe's maximum limit per request
|
||||
}
|
||||
|
||||
let hasMore = true
|
||||
let startingAfter = null
|
||||
let totalProcessed = 0
|
||||
|
||||
while (hasMore) {
|
||||
const params = { ...listParams }
|
||||
if (startingAfter) {
|
||||
params.starting_after = startingAfter
|
||||
}
|
||||
|
||||
// Get batch of subscriptions
|
||||
const subscriptions = await stripeClient.stripe.subscriptions.list(params)
|
||||
|
||||
await trackProgress(
|
||||
`Retrieved ${subscriptions.data.length} subscriptions (total processed so far: ${totalProcessed})`
|
||||
)
|
||||
|
||||
// Process each subscription in the batch
|
||||
for (const subscription of subscriptions.data) {
|
||||
await processSubscription(subscription, stripeClient, commit, verbose)
|
||||
|
||||
totalProcessed++
|
||||
|
||||
// Check if we've hit the limit
|
||||
if (limit && totalProcessed >= limit) {
|
||||
await trackProgress(`Reached limit of ${limit} subscriptions, stopping`)
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
|
||||
// Progress update every 50 subscriptions
|
||||
if (totalProcessed % 50 === 0) {
|
||||
await trackProgress(
|
||||
`Progress: ${totalProcessed} processed, ${updatedCount} subscriptions updated, ${errorCount} errors`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are more subscriptions to process
|
||||
hasMore = hasMore && subscriptions.has_more
|
||||
if (hasMore && subscriptions.data.length > 0) {
|
||||
startingAfter = subscriptions.data[subscriptions.data.length - 1].id
|
||||
}
|
||||
|
||||
// Rate limit between batch requests
|
||||
await rateLimitSleep()
|
||||
}
|
||||
|
||||
// Final summary
|
||||
await trackProgress('FINAL SUMMARY:')
|
||||
await trackProgress(` Total subscriptions processed: ${processedCount}`)
|
||||
await trackProgress(
|
||||
` Subscriptions ${commit ? 'updated' : 'would be updated'}: ${updatedCount}`
|
||||
)
|
||||
await trackProgress(` Errors encountered: ${errorCount}`)
|
||||
|
||||
if (!commit && updatedCount > 0) {
|
||||
await trackProgress('')
|
||||
await trackProgress(
|
||||
'To actually perform the updates, run the script with --commit flag'
|
||||
)
|
||||
}
|
||||
|
||||
if (errorCount > 0) {
|
||||
await trackProgress(
|
||||
'Some errors were encountered. Check the logs above for details.'
|
||||
)
|
||||
}
|
||||
|
||||
await trackProgress(`Script completed successfully in ${mode}`)
|
||||
}
|
||||
|
||||
// Execute the script using the runner
|
||||
try {
|
||||
await scriptRunner(main)
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error('Script failed:', error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user